Android 视图动画与属性动画的区别
Android的视图动画和属性动画在功能和使用上有一些明显的区别。
视图动画主要作用于视图,实现如缩放、旋转等效果。这种动画效果相对固定,只能应用于视图对象,且只能改变视图的大小和位置,而不能真正改变视图的属性。视图动画在Android 3.0以前的版本中广泛应用,但其在应用中的灵活性相对较低。
相比之下,属性动画在Android 3.0之后引入,其功能和灵活性都大大增强。属性动画可以对一个对象的属性进行操作,不仅能应用于视图对象,还能应用于任何对象。它不仅能实现缩放、旋转等效果,还能自定义动画效果,监听动画的过程,并在动画过程中或完成后执行特定的动作。属性动画通过改变对象的属性来实现动画,可以真正改变对象的属性。
总的来说,属性动画比视图动画更强大和灵活。它不仅可以实现视图动画的所有功能,还具有更多的自定义选项和更广泛的应用范围。
正文
我们知道视图动画并不能真正影响VIew的属性,而view的相对位置,大小等都是view的属性,所以视图动画并不能真正的改变view,所以属性动画便有了价值。同时之前的视图动画不能动态的改变背景颜色什么的,而属性动画却可以。下面我们来看属性动画的一些相关类:
- ValueAnimator :Animator 的子类,实现了动画的整个处理逻辑,也是属性动画最为核心的类
- ObjectAnimator:对象属性动画的操作类,继承自ValueAnimator,通过该类使用动画的形式操作对象的属性
- TimeInterpolator:时间插值器,它的作用是根据时间流逝的百分比来计算当前属性值改变的百分比,系统预置的有线性插值器、加速减速插值器、减速插值器等。
- TypeEvaluator:TypeEvaluator翻译为类型估值算法,它的作用是根据当前属性改变的百分比来计算改变后的属性值
- Property:属性对象、主要定义了属性的set和get方法
- PropertyValuesHolder:持有目标属性Property、setter和getter方法、以及关键帧集合的类。
- KeyframeSet:存储一个动画的关键帧集
ValueAnimator
那么应该如何理解ValueAnimator呢?ValueAnimator可以看做一个动画时间值的处理分发器,他逻辑上并不直接操作view的属性。我们通过监听去获取到分发下来的值去对view进行操作,所以说,一个valueAnimator 分发下来的值只建议操作一个属性,写到一起也不是不行。那么如何获取到值的分发就很重要了。
ValueAnimator主要支持以下设置:
- setDuration 设置动画执行时间。
- setRepeatCount 重复次数 当等于ValueAnimator.INFINITE 表示循环。
- setRepeatMode 设置循环播放模式,正序播放(RESTART)和逆序播放(REVERSE)。
- setStartDelay 延时开始时间,第一次生效后,重复动画过程中将不再生效。
- getAnimatedValue 获取当前运动点的值。
- start 开始动画
- cancel 取消动画
- addUodateListenner 添加动画值的变化的监听。
- addListener 添加动画监听。 开始start,结束end ,取消cancel,重复repeat
- removeUpdateListener 移除更新值的监听,removeAllUpdateListeners 移除所有更新值的监听。
- removelistener 移除动画监听,removeListeners 移除所有的动画监听
- setStartDelay 延时多久开始动画,单位毫秒。
- clone 完全克隆一个动画对象,包括他的所有设置及其监听器代码。
构造器
- static ValueAnimator ofInt(int... values)
- static ValueAnimator ofArgb(int... values)
- static ValueAnimator ofFloat(float... values)
- static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)
- static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
可以看到入参都是多个。支持int,argb,float,PropertyValuesHolder,TypeEvaluator等类型。
如何实现视图动画的效果
我们知道,视图动画支持缩放scale、alpha 透明度、rotate旋转、translate平移。而ValueAnimator 主要是基于时间将我们设置到值进行分发,所以我们需要获取到每个片的值的改变。这个就是上面提到的addUodateListenner
translate 平移效果
我们知道,view的位置的决定是由layout(l,t,r,b) 个参数决定的。
- l 视图的左边界相对于其父视图的位置。
- t 顶部
- r 右边
- 底部
所以说,我们平移,就可以通过改变view.layout 中的值进行处理。
ini
val animator=ValueAnimator.ofInt(0,400)
animator.duration = 1000
animator.addUpdateListener {
val curValue :Int= it.animatedValue as Int
binding.btn1.layout(curValue,0,curValue+binding.btn1.width,binding.btn1.height)
}
animator.start()
上面的代码,我们将一个view 在X轴方向上,从0移动到400px 的位置。整个动画耗时1秒。下面代码也是平移:
ini
val animator=ValueAnimator.ofInt(0,400)
animator.duration = 1000
animator.addUpdateListener {
val curValue :Int= it.animatedValue as Int
binding.btn1.translationX=curValue.toFloat()
}
animator.start()
那么平移还有没有其他思路,我们知道view 还有一个滚动函数。如果说是平移内容,跑马灯效果,那么就可以调用scrollTo 函数。
scale 缩放
view 也提供了scale 函数用于缩放view。那么我们尝试将整个view在1秒内从1变化到2.
ini
val animator=ValueAnimator.ofFloat(1f,2f)
animator.duration = 1000
animator.addUpdateListener {
val curValue :Float= it.animatedValue as Float
binding.btn1.scaleX=curValue
}
animator.start()
可以看到,我们只是缩放了X轴,并没有缩放Y轴,缩放Y轴则是调用scaleY
roate 旋转
view 提供了rotation、rotationX、rotationY
ini
val animator=ValueAnimator.ofFloat(0f,360f)
animator.duration = 1000
animator.addUpdateListener {
val curValue :Float= it.animatedValue as Float
//binding.btn1.rotation=curValue
binding.btn1.rotationX=curValue
//binding.btn1.rotationY=curValue
}
animator.start()
可以看到,上面的代码,我们围绕X轴进行了翻转360度。
alpha 透明度
改变透明度分为改变整个view的透明度和改变view 背景的透明度。
ini
val animator=ValueAnimator.ofFloat(1f,0f,1f)
animator.duration = 3000
animator.addUpdateListener {
val curValue :Float= it.animatedValue as Float
binding.btn1.alpha=curValue
}
animator.start()
可以看到,上面代码是将一个view的透明度从1改变到0完全透明在到1完全不透明。
ini
val animator=ValueAnimator.ofFloat(255f,0f,255f)
animator.duration = 3000
animator.addUpdateListener {
val curValue :Float= it.animatedValue as Float
binding.btn1.background.alpha=curValue.toInt()
}
animator.start()
上面代码是将view的背景从完全不透明改到完全透明,再到完全不透明。
改变颜色
我们将按钮的颜色由黑色变化到红色。
ini
val animator=ValueAnimator.ofArgb(Color.BLACK,Color.RED)
animator.duration = 1000
animator.addUpdateListener {
val curValue :Int= it.animatedValue as Int
binding.btn1.setTextColor(curValue)
}
animator.start()
}
总结
可以看到,我们上面的几乎的所有写法都是基于addUpdateListener,然后去改变view的属性。所以说,限制我们的全是想象力。这个动画只要view属性支持,我们就可以整出各种花样来。
自定义插值器与evaluator
我们在视图动画里面,知道Android 系统给我们提供了很多插值器。哪些插值器,在属性动画中同样可用。默认的插值器是LinearInterpolator。
自定义插值器
可以看懂LinearInterpolator 继承于BaseInterpolator。最终可以看到都是继承于TimeInterpolator,而这个接口只有一个函数。
arduino
float getInterpolation(float input);
而LinearInterpolator 则是原封不动的将input 给返回了,所以说,自定义插值器其实是对于input 进行计算,然后返回。在这个接口的描述中我们知道 input 的取值范围是0到1。这个用于表示动画执行时间的百分比。所以这个干预的是动画的时间。
evaluator
通过自定义插值器的描述,我们可以知道,插值器只是对于动画时长的处理。但是我们通过构造器,发现可以传入很多种类型,而对不同类型的值的分发,就靠evaluator了。所以不同的值类型有自己的evaluator.所有的evaluator都实现 TypeEvaluator 接口。默认的有:
- IntEvaluator
- FloatEvaluator
- ArgbEvaluator
这个干预的是动画分发下来的值。我们看下IntEvaluator的实现代码:
sql
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
他是开始位置+百分比*(结束位置-开始位置)。所以,evaluate会调用多次。
使用ofObject 分发想要的内容
比如说,有一个需求,分发A到Z,因为涉及到动画,所以考虑写到动画里面。那么结合上面的知识,我们就需要自定义一个Evaluator.结合IntEvaluator 的经验。这里又一个知识点。就是每个char都有一个ASCII码,所以我们通过ASCII 码进行计算。
kotlin
class CharEvaluator : TypeEvaluator<Char> {
override fun evaluate(fraction: Float, startValue: Char, endValue: Char): Char {
val start = startValue.code
val end = endValue.code
val cur = start + fraction * (end - start)
return cur.toInt().toChar()
}
}
ini
val animator=ValueAnimator.ofObject(CharEvaluator(),'A','Z')
animator.duration = 1000
animator.addUpdateListener {
val curValue :Char= it.animatedValue as Char
binding.btn1.text = curValue.toString()
}
animator.start()
propertyValuesHolder与关键帧KeyFrame
构造器里面有一个 **static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) ** 。通过阅读ValueAnimator 源码可以发现,几乎所有的构造器都会生成一个ProPertyValuesHolder 对象,这个对象其中保存了动画过程中需要操作的属性和对应的值。所以我们可以根据这个对象构造动画,大致分为一下几个大类。
- ofFloat
- ofint
- ofKeyFrame,关键帧
- ofMultFloat
- ofMultInt
- ofObject
当然包含了不同入参的重载函数,可以看到propertyValuesHolder是允许传入多个的,我使用ofInt 等每次都只能生成一个PropertyValuesHolder。
kotlin
val animator=ValueAnimator.ofPropertyValuesHolder(PropertyValuesHolder.ofFloat("a",0f,10f),PropertyValuesHolder.ofInt("b",1,50),PropertyValuesHolder.ofInt("c",100,50))
animator.duration = 1000
animator.addUpdateListener {
LogUtils.e("${it.getAnimatedValue("a")} ${it.getAnimatedValue("b")} ${it.getAnimatedValue("c")} ")
}
animator.start()
当我们设置多个Holderd的时候,可以调用 **Object getAnimatedValue(String propertyName) ** 获取对应的value。如果只有一个值,默认去的第一个,所以 it.animatedValue 就可以获取到。当然一个值也可以传递propertyName 进行获取。
keyFrame 关键帧
我们想要控制动画的速率的变化,可以通过自定义插值器或者自定义Evaluator实现。但是这个涉及到数学知识,为了更方便的解决控制动画速率的问题,Google提供了一个新的思路,Keyframe,这个和帧动画的概念类似,动画将由具体帧决定,而不是计算。而Keyframe 则依旧和ValueAnimator一样,操作的是数据。可以看到keyframe 依旧支持3种类型的重载:
- ofFloat
- ofInt
- ofObject
入参:
- fraction 表示当前显示的进度,即插值器中getInterpolation 函数的返回值。
- value 表示动画当前所在的数值位置,即animateValue .
例如:keyFrame.ofFloat(0.25f,25) 表示动画进度是百分之25,数值是25。
less
val animator=ValueAnimator.ofPropertyValuesHolder(PropertyValuesHolder.ofKeyframe("a",
Keyframe.ofInt(0f,0),
Keyframe.ofInt(0.25f,25),
Keyframe.ofInt(0.50f,50),
Keyframe.ofInt(0.75f,75),
Keyframe.ofInt(1f,100),
))
animator.duration = 1000
animator.addUpdateListener {
LogUtils.e("${it.getAnimatedValue("a")} ")
}
animator.start()
通过Log可以看到,这个同样包含匀速的插值器,所以这个也可以设置插值器。和之前的逻辑是一致的,values 的值必须大于等于2,必须保证有一个开始和一个结束,所以Keyframe 必须大于等于2。
ObjectAnimator
通过上面的例子,我们可以看到,ValueAnimator 只是对值进行了分发,一些属性的设置还需要我们自己写,于是就产生了ObjectAnimator类。结合ValueAnimator 实现动画的经验,ObjectAnimator 其实也就是在addUpdateListener 中帮助我们调用了函数,默认帮我们拼接了set,所以这个应该是反射实现的。
淡入淡出
aidl
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(binding.image, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
alphaAnim.setDuration(3000);
alphaAnim.start();
旋转
aidl
// 旋转
ObjectAnimator anim = ObjectAnimator.ofFloat(binding.image, "rotation", 0f, 360f);
// 动画时长
anim.setDuration(1000);
anim.start();
缩放
aidl
ObjectAnimator anim = ObjectAnimator.ofFloat(binding.image, "scaleX", 1.0f, 1.5f);
anim.setDuration(1000);
anim.start();
平移
aidl
ObjectAnimator transXAnim = ObjectAnimator.ofFloat(binding.image, "translationX", 100, 400);
transXAnim.setDuration(3000);
transXAnim.start();
组合同时缩放X与Y
aidl
AnimatorSet set = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(binding.image, "scaleX", 1.0f, 1.5f);
ObjectAnimator animY = ObjectAnimator.ofFloat(binding.image, "scaleY", 1.0f, 1.5f);
// 同时动
set.playTogether(animX, animY);
set.setDuration(3000);
set.start();
组合顺序缩放X与Y
aidl
AnimatorSet set = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(binding.image, "scaleX", 1.0f, 1.5f);
ObjectAnimator animY = ObjectAnimator.ofFloat(binding.image, "scaleY", 1.0f, 1.5f);
// 按照顺序动
set.playSequentially(animX, animY);
set.setDuration(3000);
set.start();
view 生命周期绑定动画
这么写的好处就是view 移除出屏幕就暂停动画了。而且便于recyclerview的复用
aidl
binding.image.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// TODO 设置动画
}
@Override
public void onViewDetachedFromWindow(View v) {
v.clearAnimation();
}
});
自定义属性与ofObject(Object target, String propertyName,
css
TypeEvaluator evaluator, Object... values)
那么,当我们需要调用view的自定义函数呢?那么就需要使用这个函数了。还是上面的哪例子,我们A到Z,设置为text。这个时候,就需要用到反射的相关知识了,setText 的入参类型是CharSequence,但是它是通过values去获取到对应的class的,但是我们传入的参数是CharSequence的实现类,所以,基于values 是永远拿不到void setText(CharSequence text)这个函数的。但是我们可以偷换概念。既然65是ASCII码的A,那么我们就用int,然后自己写一个int 入参的函数调用setText.
ini
val animator= ObjectAnimator.ofObject(this,"Text",IntEvaluator(),65,75)
animator.duration=1000
animator.start()
kotlin
fun setText(code:Int){
binding.btn1.text = code.toChar().toString()
}
当然。写一个入参类型是string 的方法也行。
组合动画 AnimatorSet
因为ValueAnimator 需要自己绑定属性,那么我们做动画的大多数都是基于ObjectAnimator。
- playAwquentially 动画依次执行。
- playTogether 动画一起执行。
- setDuration 单个动画的时长,优先级高于子动画的时长。
- setInterPolator 插值器,优先级高于子动画。
- setTarget 目标动画,这个优先级也高于子动画。
- setStartDelay 设置延时开始动画时长,仅争对AnimatorSet的激活时长,对单个动画的延时设置没有影响。
我们知道动画是否循环播放是又动画本身控制的。所以AnimatorSet 不参与动画循环播放的控制。
animatorSet.builder
用于更精细化的控制组合动画的播放。
- play 播放那个动画
- with 和前面动画一起播放。
- before 先执行这个动画,再执行前面的动画。
- after 前面的动画执行完成后,才执行这个动画
- after 延迟n毫秒后执行动画。
animatorSet 监听器AnimatorListener
- onAnimationStart 开始
- end 结束
- cancel 取消
- repeat 重复
ViewPropertyAnimator
通过ObjectAnimator 可以便捷的设置属性动画,那么还有没有更便捷的呢?那就是ViewPropertyAnimator。
scss
binding.btn1.animate().setDuration(1000).translationX(10f).rotationX(360f).start()
因为这个并没有像ObjectAnimator 一样使用反射,而是通过计算出具体的的属性值,然后调用invalidata() 函数进行重绘,性能是要比ObjectAnimator 高一些,但是反射的性能影响微乎其微,所以他的优势是链式调度与简化了代码的读写。
在xml中的实现
如何通过XML 实现ValueAnimator,ObjectAnimator,AnimatorSet。我们通常不使用ValueAnimator,而是使用ObjectAnimator。
ValueAnimator
对应的标签为animator。通过animatorInflater.loadAnimator() 获取xml中的属性动画。
- duration 动画时长。
- valueFrom 初始动画的值,为float,int和color种类型。
- valueTo 同样3个类型的值。
- startOffset 动画激活延时,单位毫秒。
- repeatConunt 重复次数,infinite 表示无限循环
- repeatMode 重复模式。repeat 正序,reverse 倒序。
- valueType 表示数值类型,和to、from 对应,如果是color 就不需要设置这个值。
- interpolator 设置插值器。
objectAnimator 标签
和animator 标签类似,只是多了一个属性:
- propertyName 对应要操作的属性的名称。
set标签
这个在属性动画中特指animatorSet,用于实现多个属性动画的组合。
总结
整体的属性动画都是还是基于一个分发器和操作view的属性去实现的。通常而言,还是写到代码里面的时候居多,而且通过ViewPropertyAnimator也可以实现一些简单的组合动画什么的。