(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - joe
链接:http://android.jobbole.com/84466/
点击 → 了解如何加入专栏作者
开头
这是自定义View和动画的第二篇,第一篇是《
Android drawPath 实现QQ拖拽泡泡
》,主要介绍了drawPath 绘制二次贝塞尔曲线的过程。(idea来自同事的新需求!)
话不多说,还是先上效果图吧!(手贱升级了Genymotion,就成这个傻逼样子了!)
效果图
全局配置
根据效果图,再来说说实现的基本过程。上面的Bitmap 的动画就是使用了属性动画ObjectAnimator,而下面的那个跳动的文字,主要就是使用了drawTextOnPath的方法,其实也是基于第一篇讲解的drawPath来实现的!所以总的来说就是属性动画+drawTextViewPath
动画介绍
这里一共定义了三个属性动画:
private
ObjectAnimator
distanceDownAnimator
;
//图片下降的动画
private
ObjectAnimator
distanceUpAnimator
;
//图片上升的动画
private
ObjectAnimator
offsetAnimator
;
//文字偏移动画
动画这里还要随便提一嘴动画插补器Interpolator
private
DecelerateInterpolator
decelerateInterpolator
=
new
DecelerateInterpolator
();
//减速插补器
private
LinearInterpolator
linearInterpolator
=
new
LinearInterpolator
();
//加速插补器
private
LinearOutSlowInInterpolator
linearOutSlowInInterpolator
=
new
LinearOutSlowInInterpolator
();
private
FastOutSlowInInterpolator
fastOutSlowInInterpolator
=
new
FastOutSlowInInterpolator
();
private
BounceInterpolator
bounceInterpolator
=
new
BounceInterpolator
();
//反弹插补器
详细的请看这个兄弟的博客,有配图,很形象直观的!
http://blog.csdn.net/pengkv/article/details/50488171
这里的话,图片下落肯定是一个重力加速的过程 使用了LinearInterpolator,而上升的话,肯定是一个减速的过程,使用了DecelerateInterpolator,而文字的跳动,那就非BounceInterpolator 莫属了!
到这里,动画的基础讲解暂告一段落。
drawTextOnPath 方法使用介绍
Draw the text, with origin at (x,y), using the specified paint, along the specified path. The paint’s Align setting determins where along the path to start the text.
其实这个方法就是drawText() 的方法的基础上,沿着指定的路径来绘制对应的文字!
drawTextOnPath热身
对应的代码:
path
.
reset
();
path
.
moveTo
(
100
,
100
);
path
.
lineTo
(
300
,
200
);
path
.
lineTo
(
700
,
600
);
canvas
.
translate
(
0
,
100
);
paint
.
setStyle
(
Paint
.
Style
.
FILL
);
canvas
.
drawText
(
TEST
,
0
,
0
,
paint
);
//直接画文字 第一个
canvas
.
translate
(
0
,
300
);
canvas
.
drawTextOnPath
(
TEST
,
path
,
0
,
0
,
paint
);
//第二组
paint
.
setStyle
(
Paint
.
Style
.
STROKE
);
canvas
.
drawPath
(
path
,
paint
);
path
.
reset
();
path
.
moveTo
(
0
,
500
);
path
.
quadTo
(
400
,
800
,
800
,
500
);
paint
.
setStyle
(
Paint
.
Style
.
FILL
);
canvas
.
drawTextOnPath
(
TEST
,
path
,
0
,
0
,
paint
);
//第三组,这个也差不多就是后面需要实现的效果了!
paint
.
setStyle
(
Paint
.
Style
.
STROKE
);
canvas
.
drawPath
(
path
,
paint
);
啦啦啦,通过热身,可以清楚的看到,要想实现跳动的文字其实很简单啦,就是动态的改变Path的路径,然后在这个路径上不断绘制出文字就好了!原理说着都是枯燥的,直接撸上代码!
OffsetAnimator && OffsetProperty
如上面的介绍,这个Animator就是来控制Path的绘制的。
offsetAnimator
=
ObjectAnimator
.
ofFloat
(
this
,
mOffsetProperty
,
0
);
offsetAnimator
.
setDuration
(
300
);
offsetAnimator
.
setInterpolator
(
bounceInterpolator
);
这里使用了自定义的属性OffsetProperty,这个是什么鬼呢?其实就是一个自己定义的属性啦!
private
Property
PathTextView
,
Float
>
mOffsetProperty
=
new
Property
PathTextView
,
Float
>
(
Float
.
class
,
"offset"
)
{
@Override
public
Float
get
(
PathTextView
object
)
{
return
object
.
getCurrentOffset
();
}
@Override
public
void
set
(
PathTextView
object
,
Float
value
)
{
object
.
setCurrentOffset
(
value
);
}
};
public
void
setCurrentOffset
(
Float
currentOffset
)
{
this
.
currentOffset
=
currentOffset
;
invalidate
();
}
就是通过属性动画,得到新的currentOffset,然后再调用 invalidate() 不停的重画!在onDraw() 方法里,有一下代码片段来更新path,然后根据path绘制文字!!
if
(
currentOffset
!= -
1
)
{
path
.
quadTo
(
dXXX
==
0
?
radioCenterX
:
radioCenterX
+
dXXX
,
currentOffset
,
textWidth
,
defaultY
);
}
else
{
path
.
lineTo
(
textWidth
,
defaultY
);
}
...
canvas
.
drawTextOnPath
(
TEST
,
path
,
0
,
0
,
textPaint
);
嗯,说到这里,其实今天要讲的跳动的问题,其实跳动的文字基本上就OK啦,但是水果忍者的话,就是接下来的重点实现了。
水果忍者
我们这里一共有三个动画:
private
ObjectAnimator
distanceDownAnimator
;
//图片下降的动画
private
ObjectAnimator
distanceUpAnimator
;
//图片上升的动画
private
ObjectAnimator
offsetAnimator
;
//文字偏移动画
动画的流程
distanceDownAnimator.start —> distanceDownAnimator.onEnd —> distanceUpAnimator.start && offsetAnimator.start —> distanceUpAnimator.end —> distanceDownAnimator.start
顺便提一嘴动画的回调监听:
distanceUpAnimator
.
addListener
(
new
SimpleAnimatorListener
()
{
@Override
public
void
onAnimationStart
(
Animator
animation
)
{
isUp
=
true
;
left
= !
left
;
}
@Override
public
void
onAnimationEnd
(
Animator
animation
)
{
distanceDownAnimator
.
start
();
}
});
distanceDownAnimator
.
addListener
(
new
SimpleAnimatorListener
()
{
@Override
public
void
onAnimationStart
(
Animator
animation
)
{
isUp
=
false
;
dXXX
=
0
;
if
(
++
currentIndex
>=
bitmaps
.
size
())
{
currentIndex
=
0
;
}
currentBitmap
=
bitmaps
.
get
(
currentIndex
);
radioCenterY
=
currentBitmap
.
getHeight
()
/
2.0f
;
}
@Override
public
void
onAnimationEnd
(
Animator
animation
)
{
offsetAnimator
.
cancel
();
offsetAnimator
.
setDuration
(
200
);
offsetAnimator
.
setFloatValues
(
defaultY
,
defaultY
+
amplitude
,
defaultY
);
offsetAnimator
.
start
();
distanceUpAnimator
.
start
();
}
});
效果图可以看到,目前我一共设计了三种水果的动画,先从简单的竖直方向掉落又上升说起吧!
这里面其实就是两个动画,一个Y轴的平移,一个是自身的旋转。
相关问题明确
Q1: 水平方向中心怎么确定?
其实就是确认布局的宽度,布局的宽度就是文字的宽度
@Override
protected
void
onMeasure
(
int
widthMeasureSpec
,
int
heightMeasureSpec
)
{
textHeight
=
textPaint
.
getFontMetrics
().
bottom
-
textPaint
.
getFontMetrics
().
top
;
widthMeasureSpec
=
MeasureSpec
.
makeMeasureSpec
((
int
)
textPaint
.
measureText
(
TEST
),
MeasureSpec
.
EXACTLY
);
//强制使用精准的测量模式
super
.
onMeasure
(
widthMeasureSpec
,
heightMeasureSpec
);
}
Q2: 竖直方向起始位置和终点位置怎么确认?
其实就是确认文字的高度(下落的终点),(图片的高度(下落的起点))
@Override
protected
void
onSizeChanged
(
int
w
,
int
h
,
int
oldw
,
int
oldh
)
{
Log
.
i
(
TAG
,
"onSizeChanged: size改变了!!!!"
);
super
.
onSizeChanged
(
w
,
h
,
oldw
,
oldh
);
currentHeight
=
h
;
initAnim
(
h
);
}
private
void
initAnim
(
int
currentHeight
)
{
textHeight
=
textPaint
.
getFontMetrics
().
bottom
-
textPaint
.
getFontMetrics
().
top
;
//文字的高度获取
defaultY
=
currentHeight
-
textHeight
;
//默认的最低处,到文字的顶部
offsetAnimator
.
setFloatValues
(
defaultY
,
defaultY
+
amplitude
,
defaultY
);
radioCenterY
=
currentBitmap
.
getHeight
()
/
2.0f
;
//初始化默认高度
distanceDownAnimator
.
setFloatValues
(
radioCenterY
,
defaultY
);
....
}
Q3:旋转的动画没有对应的Animator,如果控制?
直接获取 distanceDownAnimator 或者 distanceUpAnimator 的动画执行百分比:
distanceDownAnimator
.
addUpdateListener
(
new
ValueAnimator
.
AnimatorUpdateListener
()
{
@Override
public
void
onAnimationUpdate
(
ValueAnimator
animation
)
{
fraction
=
animation
.
getAnimatedFraction
();
}
});
Q4:旋转的中心点怎么确认?
图片宽高的一半(如果有水平方向移动也要加上水平偏移量)
float
dX
=
(
left
?
radioCenterX
*
fraction
:
radioCenterX
*
fraction
* -
1.0f
);
//相对于中心点 0 的水平偏移量
radioCenterX
=
(
defaultX
+
textWidth
)
/
2.0f
;
radioCenterY
=
currentBitmap
.
getHeight
()
/
2.0f
;
canvas
.
rotate
(
360
*
fraction
,
radioCenterX
+
dX
,
radioCenterY
);
Q5:图片切换如何实现的?
使用一个集合管理了所有的Bitmap,在Down动画开始执行的时候,去更新当前的图片!
final
ArrayList
bitmaps
=
new
ArrayList
();
bitmaps
.
add
(
BitmapFactory
.
decodeResource
(
getResources
(),
R
.
drawable
.
fruit1
));
bitmaps
.
add
(
BitmapFactory
.
decodeResource
(
getResources
(),
R
.
drawable
.
fruit2
));
bitmaps
.
add
(
BitmapFactory
.
decodeResource
(
getResources
(),
R
.
drawable
.
fruit3
));
...
if
(
++
currentIndex
>=
bitmaps
.
size
())
{
currentIndex
=
0
;
}
currentBitmap
=
bitmaps
.
get
(
currentIndex
);
到这里,起点位置,终点位置以及旋转中心点位置已经确认完毕了!无论是下降,还是上升的动画,都是不断在改变radioCenterY 的值,原理同之前介绍的offset相同
private
Property
PathTextView
,
Float
>
mDistanceProperty
=
new
Property
PathTextView
,
Float
>
(
Float
.
class
,
"distance"
)
{
@Override
public
Float
get