引言
属性动画是Android 3.0(API 11)加入的,如果想在之前的版本使用属性动画,建议使用nineoldandroids,网址是:http://nineoldandroids.com。
属性动画的加入产生了一个新的问题,为什么要加入属性动画呢?
View动画虽然简单,但也存在着一些问题:
①只能作用于View对象。
②不能改变真是属性值,而只是改变了视觉效果。
③只有四种动画效果,效果种类少。
而属性动画(Property Animation)是一个强大的动画框架,允许作用在任意对象上。可以随着时间的变化改变任何对象的属性,不管这个对象是否绘制在屏幕上,然后通过对属性的应用而达到动画的效果。
1 ValueAnimator
ValueAnimator是属性动画重要的类,ObjectAnimator也是继承自ValueAnimator,ValueAnimator有五个常用的方法。
图1-1 ValueAnimator方法1.1 ofFloat、ofInt、OfArgb
ofFloat构造并返回一个在float值之间动画的ValueAnimator。传入一个参数时这个参数就是目标值,但是只有一个参数的情况很少用,因为无法确定起始值是什么。所以通常情况下,需要传入两个或者以上的参数。先看看使用方法,如下代码所示。
var anim: ValueAnimator = ValueAnimator.ofFloat(1f,0f,1f)
anim.duration =3000
anim.start()
方法很简单,动画开始之后可以使用addUpdateListener方法添加AnimatorUpdateListener对动画进行监听。addUpdateListener方法是很重要的方法,因为ValueAnimator主要是对值进行操作的动画,因此需要通过监听去获取相应的值来进行操作以完成动画效果。
anim.addUpdateListener{
var value =it.animatedValue as Float
Log.d("MainActivity", value.toString())
}
日志比较长我就不做截图或者复制了。从日志上可以看出来,值得变化是从1.0f开始然过渡到0.0f再过渡到1.0f。因为没有设置插值器,因此使用的是默认插值器AccelerateDecelerateInterpolator,这个插值器的效果是开始和结束慢中间加速。将value应用在Alpha上可以呈现如下图1-2效果。
图1-2 ofFloat应用效果图private fun myOfFloat() {
var anim: ValueAnimator = ValueAnimator.ofFloat(1.0f,0.0f,1.0f)
anim.duration = 3000
tv.setOnClickListener{
anim.start()
}
anim.addUpdateListener{
var value =it.animatedValue as Float
Log.d("MainActivity", value.toString())
tv.alpha = value
}
}
在某些情况下也许你并不需要float类型,而只是需要使用Int类型,就可以使用ofInt方法。先看看使用方法,代码如下所示:
var anim:ValueAnimator = ValueAnimator.ofInt(0,1)
anim.duration =1000
anim.start()
ofArgb构造并返回一个在颜色值之间变化的ValueAnimator。ofArgb其实和ofInt本质上是一样的,可以从下图1-3、1-4源码可看出来,ofArgb只是设置了估值器(Evaluator), ArgbEvaluator是一个估值器,它决定了ofArgb传入的值是如何变化的。
图1-3 ofInt源码 图1-4 ofArgb源码使用ofArgb的方式也很简单,例如下面方法,传入两个32位颜色值即可达到图1-5效果。
图1-5 ofArgb效果private fun myOfArgb() {
var startColor = resources.getColor(R.color.colorAccent)
var enColor = resources.getColor(R.color.colorPrimary)
var anim: ValueAnimator = ValueAnimator.ofArgb(startColor,enColor)
anim.duration =3000
tv.setOnClickListener{
anim.start()
}
anim.addUpdateListener{
var color: Int =it.animatedValue as Int
tv.setBackgroundColor(color)
}
}
1.2 ofObject
ofObject构造并返回一个在Object对象之间变化的ValueAnimator。通过下面图1-6源码可以看出来,ofObject至少需要传两个参数。
图1-6 ofObject源码evaluator 传入的是 null,Object只能传入两种类型,一种是float,另一种是int类型,传入其他的类型会把报错。可以在动画开始时初始化中看到相关的操作,源码如下:
图1-6 动画初始化操作源码再往下查看源码,可以看出来sIntEvaluator是一个IntEvaluator,sFloatEvaluator是一个FloatEvaluator。
图1-7 sIntEvaluator、sFloatEvaluator 实例化如果evaluator 传入的是 null,Object传入的是float类型,则可以把ofObject当成ofFloat使用,如下所示:
var anim: ValueAnimator = ValueAnimator.ofObject(null,0.0f,1.0f)
如果TypeEvaluator传入的是ArgbEvaluator,Object传入的是32位颜色值,则可以把ofObject当成ofArgb使用,如下所示:
var startColor =resources.getColor(R.color.colorAccent)
var enColor =resources.getColor(R.color.colorPrimary)
var anim: ValueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), startColor, enColor)
ofObject的用处不止于此,它强大的地方在于可以自定义TypeEvaluator,然后传入任意对象。本文将下一小节进行讲解。
1.3 TypeEvaluator
看了这么多关于TypeEvaluator的应用,肯定会很好奇TypeEvaluator到底是个什么东西。TypeEvaluator其实是一个接口,只有evaluate一个方法。
图1-8 TypeEvaluator以IntEvaluator为例子,可以看出来IntEvaluator实现了TypeEvaluator。
图1-9 IntEvaluator源码从上面IntEvaluator的源码可以看出,evaluate方法对值做了处理,其中fraction是变化的百分比,然后从startValue开始按照线性速率向endValue变化。因此,如果想要自定义TypeEvaluator ,只需要在evaluate方法上下功夫就可以了。
1.4 ofPropertyValuesHolder
ofPropertyValuesHolder方法构造并返回一个指定在PropertyValuesHolder内的值进行动画的ValueAnimator。通过下面的源码可以看出来,ofPropertyValuesHolder需要传入至少一个PropertyValuesHolder。
图1-10 ofPropertyValuesHolder源码那PropertyValuesHolder又是什么呢?PropertyValuesHolder类保存有关属性的信息以及该属性在动画中应该呈现的值。PropertyValuesHolder对象可用于使用ValueAnimator或ObjectAnimator创建并行操作多个不同属性的动画。
PropertyValuesHolder的方法有很多,图1-11的方法是最重要的。
图1-11 PropertyValuesHolder方法首先看ofFloat的使用方法,下面的代码先实例化了两个PropertyValuesHolder对象,然后再把这两个对象传入ofPropertyValuesHolder生成一个ValueAnimator。
private fun startOfPropertyValuesHolder() {
val valuesHolderX = PropertyValuesHolder.ofFloat("getX",0.0f,10.0f)
val valuesHolderY = PropertyValuesHolder.ofFloat("getY",0.0f,100.0f)
val anim = ValueAnimator.ofPropertyValuesHolder(valuesHolderX,valuesHolderY)
anim.duration =3000
anim.addUpdateListener{
var getX = it.getAnimatedValue("getX") as Float
}
anim.start()
}
上面的代码中,查看源码发现通过getAnimatedValue(String propertyName)方法能获取指定的propertyName相对应的PropertyValuesHolder的值。还可以通过查看源码知道,使用getAnimatedValue()只能获取第一个PropertyValuesHolder的值。
图1-12 getAnimatedValue(String propertyName) 源码 图1-13 getAnimatedValue()源码此时可能会想到,之前使用的ofFloat不是也可以传递多值吗?它又是怎么操作的?
其实查看ofFloat源码,如下图1-14就可以发现,ofFloat传递多值也是实例化了一个PropertyValuesHolder对象。因为没有产生多个PropertyValuesHolder对象,所以通过getAnimatedValue()方法获取到的只是一个PropertyValuesHolder对象的值。
1-14 ofFloat源码再来看看ofKeyframe方法,个人认为ofKeyframe方法是PropertyValuesHolder最重要的方法。ofKeyframe能更简单的实现动画,而不需要使用自定义TypeEvaluator。
Keyframe这个类包含一个动画的 time/value对,ValueAnimator使用Keyframe定义动画目标在整个动画过程中所要用的值,ValueAnimator在当前Keyframe的值和下一个Keyframe的值之间动画。
先看使用ofKeyframe方法的效果图,如1-15所示。
图1-15 Keyframe使用效果图再看看具体代码是怎么写的,代码如下所示:
private fun startKeyFrame() {
var progressBar2 = findViewById(R.id.progressBar3)
var key1 = Keyframe.ofInt(0.0f, 0)
var key2 = Keyframe.ofInt(0.5f, 100)
var key3 = Keyframe.ofInt(1f, 80)
var propertyValuesHolder = PropertyValuesHolder.ofKeyframe("progress", key1, key2, key3)
var anim = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder)
anim.duration =3000
var textView = findViewById(R.id.textView)
textView.setOnClickListener{
anim.start()
}
anim.addUpdateListener{
var value =it.animatedValue as Int
progressBar2.progress = value
textView.text = value.toString().plus("%")
}
}
可以看见,首先使用Keyframe.ofInt获取Keyframe对象,传入两个参数,第一个参数是动画的百分比,第二个参数是相对应的数据值,接着就是创建PropertyValuesHolder传入OfPropertyValuesHolder方法中,最后可以通过addUpdateListener方法获取动画数值对progressBar进行操作完成动画效果。
其它方法我就不做过多的说明了,因为我想不到有什么实例,哈哈哈哈~。但有几个参数还是要了解:
TypeConverter<T, V>:该类是一个抽象类,可将类型T转换成类型V,当动画中的属性类型和值类型不相同时这是必要的,它的核心方法是convert。
Property:该类是一个抽象类,用于表示宿主对象的可变值。
Path:传入Path表示沿着path动画。
2 ObjectAnimator
ObjectAnimator继承自ValueAnimator,支持目标对象上的属性进行动画。该类的构造函数使用参数定义将要动画的目标对象以及动画的属性名称,然后在内部选取合适的set/get方法,然后根据需要调用动画的属性。
在代码中实现ObjectAnimator,代码如下所示,效果图如图2-1:
图2-1 alpha效果图var anim = ObjectAnimator.ofFloat(textView, "Alpha", 1.0f, 0.0f, 1.0f)
anim.duration =1500
textView.setOnClickListener{
anim.start()
}
上面代码中,使用ofFloat构造ObjectAnimator,第一个参数就是目标对象,第二个参数是属性名,第三、四、五参数是具体动画数值。动画是如何实现的呢?是找到textView中的alpha属性进行赋值使用吗?但其实textview没有alpha属性、view也没有alpha属性。
实际上alpha这个属性名用于反射,通过属性名获取setAlpha方法来使用,查看源码发现TextView中没有setAlpha方法,但是View中有setAlpha方法,因此实际上是调用了View的setAlpha方法。
同理,得到下面的例子:
var anim = ObjectAnimator.ofFloat(textView, "TranslationX", 0f,200f) //对象x轴从0移动到200
var anim = ObjectAnimator.ofFloat(textView, "ScaleX", 1.5f)//对象x轴方法1.5倍
var anim = ObjectAnimator.ofFloat(textView, "Rotation", 0f,360f)//对象旋转360度
我们也可以在自定义View中使用这种方法,但是需要注意set方法后的第一个字符必须是大写的(驼峰命名法)。下面是自定义view的源代码,在onDraw中画了一条线,并提供了setPosition方法改变线的X轴。
class LineView : View {
private var mPosition =0f
constructor(context: Context?) :this(context, null)
constructor(context: Context?, attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
var paint= Paint()
paint.strokeWidth =20f
paint.color =resources.getColor(R.color.colorAccent,null)
canvas.drawLine(0f, 0f, mPosition, 0f, paint)
}
fun setPosition(position: Float) {
mPosition = position
invalidate()
}
}
接着实例化ObjectAnimator对象,可以看见ofFloat使用了“Position”作为属性名,并且从X轴从0f过渡到1000f。效果图如图2-2所示:
图2-2 LineView效果图var lineView = findViewById(R.id.lineView)
var anim = ObjectAnimator.ofFloat(lineView, "Position", 0f, 1000f)
anim.duration =1000
anim.start()
再来看看get方法,在LineView中加入get方法,如下所示:
fun getPosition(): Float {
return 500f
}
get方法是用来获取初始值用的,当传入的参数是一个参数的时候就需要使用到get方法获取初始值。
var anim = ObjectAnimator.ofFloat(lineView, "Position", 1000f)
动画首先从500开始过渡到1000,并不是从0开始过渡到1000。看看效果图,如下图2-3所示:
图2-3 LineView使用get方法效果图如果不提供get方法程序会报错吗?程序该从哪开始?答案是如果不提供get方法,程序会出现如下提示,但是程序仍然能正常运行,并且从初始值0开始过渡到1000,如图2-2所示。
2018-10-26 11:37:12.429 11681-11681/com.zhj.ktdemo W/PropertyValuesHolder: Method getPosition() with type null not found on target class class com.zhj.ktdemo.LineView
3 AnimatorSet
AnimatorSet是一个按照顺序执行Animator对象的类。可以一起播放动画,也可以按顺序播放,还可以延时播放。
有两种不同的方法可以添加动画到AnimatorSet,可以使用playTogether或者playSequentially方法一次添加一组动画,也可以将play方法与Builder类中的方法结合,逐个添加动画。
使用Animation.play(Aniamtor)返回一个Builder的实例,也可以直接使用Animation.Builder(Animator),他们是一样的。接着就可以使用Builder下的方法配合传入的Animator使用。
Builder类有四个重要的方法,如下图3-1所示:
图3-1 Builder类after(Animator):将当前的动画插入到传入的动画之后执行
例如:animatorSet.play(anim).after(anim2).after(anim3),anima2在anim3之后执行,anim在anim2之后执行
after(long):将动画延迟执行
例如:animatorSet.play(anim).after(3000),anim延时3000毫秒之后执行
before(Animator):将之前的动画插入到传入的动画之前执行
例如:animatorSet.play(anim).before(anim2).before(anim3),anim在anim2之前执行,anim2在anim3之前执行
with(Animator):将当前动画和传入的动画同时执行
例如:animatorSet.with(anim).with(anim2).with(anim3),三个动画同时执行
可以看具体的使用,代码如下所示:
var textView = findViewById(R.id.textView)
var anim = ObjectAnimator.ofFloat(textView, "TranslationX", -500f, 0f)
anim.duration =1000
var anim2 = ObjectAnimator.ofFloat(textView, "Alpha", 0f, 1f)
anim2.duration =1000
var anim3 = ObjectAnimator.ofFloat(textView, "ScaleX", 1f, 2f)
anim3.duration =2000
var anim4 = ObjectAnimator.ofFloat(textView, "ScaleY", 1f, 2f)
anim3.duration =2000
var animatorSet = AnimatorSet()
animatorSet.play(anim).with(anim2).after(anim3).after(anim4)
textView.setOnClickListener{
animatorSet.start()
}
从效果图3-2可以看出来,after(anim4)执行在play(anim).with(anim2).after(anim3)之前,after(anim3)执行在play(anim).with(anim2)之前,play(anim).with(anim2)同时执行。
图3-2 AnimatorSet效果图如果有需要,还可以继续使用addListener添加动画监听,代码如下所示:
animatorSet.addListener(object :Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}
4 总结
ValueAnimator继承自Animator,主要是对属性值进行操作。配合自定义估值器TypeEvaluator,可以自定义属性值进行动画。
ObjectAnimator继承自ValueAnimator,可以直接操作对象,只要对象中有合适的set方法,可以利用其关键字符当属性名。例如:var anim = ObjectAnimator.ofFloat(textView, "Alpha", 0f, 1f),因为View中setAlpha方法,因此可使用Alpha当属性名。如果View中提供get方法,当只传入一个数值时,可使用get方法获取初始值。例如:var anim = ObjectAnimator.ofFloat(lineView, "Position", 1000f),因为lineView中有getPosition方法,从该方法获取的值可当初始值使用。
AnimatorSet继承自Animator,可以将ValueAnimator和ObjectAnimator任意组合播放。可使用Play方法获取Builder类,配合Builder类的四个方法实现动画的多种效果。
网友评论