android动画(3)属性动画
1.官方文档
https://developer.android.com/guide/topics/graphics/prop-animation
属性动画系统是一个框架,用于为几乎任何对象添加动画效果,无论其是否绘制到屏幕上。属性动画会在指定时长内更改对象某个属性的值。要添加动画效果,请指定要添加动画效果的对象属性,例如对象在屏幕上的位置、动画效果持续多长时间以及要在哪些值之间添加动画效果。
2.属性动画与视图动画的区别
- 视图动画系统仅为
View
对象添加动画效果的功能 - 视图动画系统的只会在绘制视图的位置进行修改,而不会修改实际的视图本身。例如,如果为某个按钮添加了动画效果,使其可以在屏幕上移动,该按钮会正确绘制,但能够点击按钮的实际位置并不会更改,因此必须通过实现自己的逻辑来处理此事件。
3.属性动画系统中的主要类
TimeInterpolator :
动画插值器,指定运动方式(如保持匀速、先加速后减速等)。- TypeEvaluator :属性的类型及如何计算正在动画中属性的值
- duration :时长,默认时长为 300 毫秒
- startProperty :属性的起始值
- endProperty :属性的结束值
- start():开始执行动画
3.1 ValueAnimator
主计时引擎,它具有计算动画值所需的所有核心功能,同时包含每个动画的计时详情、有关动画是否重复播放的信息、用于接收更新事件的监听器以及设置待评估自定义类型的功能。
3.2 ObjectAnimator
ValueAnimator
的子类,用于设置目标对象和属性。此类会在计算出动画的新值后相应地更新属性。在大多数情况下,使用 ObjectAnimator就可以完成工作
。
注意事项
- 对象属性必须具有
set<PropertyName>()
形式的 setter 函数。 - 对象属性必须具有
get<PropertyName>()
形式的 getter 函数 - 属性名字以setter、getter为准,如对象实际属性名为mTextColor,而对应的setter为setTextColor,则要用textColor,而不是用mTextColor
- getter、setter参数类型与ofXX 类型匹配
- 如果ofXXX系列方法中仅为
values...
参数指定了一个值,则系统会假定它是动画的结束值。
3.3 AnimatorSet
复合属性动画。把多个animator组合到一起。如下:
1 val bouncer = AnimatorSet().apply { 2 play(bounceAnim).before(squashAnim1) 3 play(squashAnim1).with(squashAnim2) 4 play(squashAnim1).with(stretchAnim1) 5 play(squashAnim1).with(stretchAnim2) 6 play(bounceBackAnim).after(stretchAnim2) 7 } 8 val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply { 9 duration = 250 10 } 11 AnimatorSet().apply { 12 play(bouncer).before(fadeAnim) 13 start() 14 }
3.4 求值器
IntEvaluator |
int求值器 |
FloatEvaluator |
float求值器 |
ArgbEvaluator |
颜色属性(用十六进制值表示)求值器 |
自定义 |
参阅使用 TypeEvaluator 部分。 |
3.5 时间插值器
AccelerateDecelerateInterpolator |
该插值器的变化率在开始和结束时缓慢但在中间会加快。 |
AccelerateInterpolator |
该插值器的变化率在开始时较为缓慢,然后会加快。 |
AnticipateInterpolator |
该插值器先反向变化,然后再急速正向变化。 |
AnticipateOvershootInterpolator |
该插值器先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值。 |
BounceInterpolator |
该插值器的变化会跳过结尾处。 |
CycleInterpolator |
该插值器的动画会在指定数量的周期内重复。 |
DecelerateInterpolator |
该插值器的变化率开始很快,然后减速。 |
LinearInterpolator |
该插值器的变化率恒定不变。 |
OvershootInterpolator |
该插值器会急速正向变化,再超出最终值,然后返回。 |
TimeInterpolator 用于自定义插值器 |
该接口用于实现您自己的插值器。 https://developer.android.com/guide/topics/graphics/prop-animation#interpolators |
3.6 Animator的回调接口
- ValueAnimator.AnimatorUpdateListener 属性更新接口
- Animator.AnimatorListener 属性动画的完整接口,包括(开始、结束、取消、重复)
- Animator.AnimatorPauseListener 动画暂停、重新开始
- AnimatorListenerAdapter 一个抽象类(动画开始、结束)
3.7 KeyFrame
用来指定动画中的某一帧为关键帧,并自定义其中的行为或状态等。
1 private fun onKeyFrameClicked(v : View){ 2 val kf0 = Keyframe.ofFloat(0f, 0f) //第1帧 3 val kf1 = Keyframe.ofFloat(0.5f, 360f) //中间帧 4 val kf2 = Keyframe.ofFloat(1f, 0f) //最后1帧 5 val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2) 6 ObjectAnimator.ofPropertyValuesHolder(binding.animatorTitle, pvhRotation).apply { 7 duration = 5000 8 // interpolator = OvershootInterpolator() 9 // interpolator = CycleInterpolator(0.5f) 10 interpolator = AccelerateDecelerateInterpolator() 11 start() 12 } 13 }
4.View 对象使用属性动画
View类也可以使用属性动画,通过animate()方法返回一个ViewPropertyAnimator 对象,它就是view使用属性动画的简化类,直接使用相应的动画方法就可以了。
详见:https://developer.android.com/reference/android/view/ViewPropertyAnimator
4.2 示例
1 private fun onViewAnimate(v : View){ 2 val x = textView.translationX 3 textView.animate().translationX(x + 20f) 4 }
使用了translationX动画。
4.2 View基类已经支持的属性
translationX 和 translationY | 控制视图所在的位置,值为视图的布局容器所设置的左侧坐标和顶部坐标的 |
rotation、rotationX 和 rotationY | 控制视图围绕轴心点进行的 2D( 属性)和 3D 旋转。 |
scaleX 和 scaleY | 控制视图围绕其轴心点进行的 2D 缩放。 |
pivotX 和 pivotY |
控制旋转和缩放转换所围绕的轴心点的位置。默认情况下,轴心点位于对象的中心 |
x 和 y |
描述视图在容器中的最终位置,值分别为左侧值与 translationX 值的和以及顶部值与 translationY 值的和。 |
alpha | 表示视图的 Alpha 透明度。此值默认为 1(不透明),值为 0 则表示完全透明(不可见) |
view的子类 参见其中的setter与getter,如TextView的textColor属性
5.代码中直接使用属性动画
5.1 ValueAnimator和相关listener
1 val animatorUpdate = object : ValueAnimator.AnimatorUpdateListener { 2 override fun onAnimationUpdate(animation: ValueAnimator?) { 3 val value = animation?.animatedValue 4 Log.e("MainActivity","AnimatorUpdateListener value = $value") 5 } 6 } 7 val animatorListener = object : Animator.AnimatorListener{ 8 override fun onAnimationStart(animation: Animator?) { 9 Log.e("MainActivity","AnimatorListener onAnimationStart") 10 } 11 12 override fun onAnimationEnd(animation: Animator?) { 13 Log.e("MainActivity","AnimatorListener onAnimationEnd") 14 } 15 16 override fun onAnimationCancel(animation: Animator?) { 17 Log.e("MainActivity","AnimatorListener onAnimationCancel") 18 } 19 20 override fun onAnimationRepeat(animation: Animator?) { 21 Log.e("MainActivity","AnimatorListener onAnimationRepeat") 22 } 23 } 24 val animatorAdapter = object : AnimatorListenerAdapter(){ 25 override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { 26 super.onAnimationStart(animation, isReverse) 27 Log.e("MainActivity","AnimatorListenerAdapter onAnimationStart") 28 } 29 30 override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) { 31 super.onAnimationEnd(animation, isReverse) 32 Log.e("MainActivity","AnimatorListenerAdapter onAnimationEnd") 33 } 34 } 35 ValueAnimator.ofFloat(0f, 100f).apply { 36 addUpdateListener(animatorUpdate) 37 addListener(animatorAdapter) 38 interpolator = DecelerateInterpolator() 39 duration = 1000 40 start() 41 }
5.2 ObjectAnimator
1 val translation = ObjectAnimator.ofFloat(binding.animatorTitle, "translationX", end).apply { 2 duration = 1000 3 interpolator = AnticipateInterpolator() 4 start() 5 }
5.3 AnimatorSet
可顺序播放,也可以自定义播放顺序。
1 //动画1 2 val end = if(binding.animatorTitle.translationX < binding.root.measuredWidth / 2) binding.root.measuredWidth / 2f else 0f 3 val translation = ObjectAnimator.ofFloat(binding.animatorTitle, "translationX", end).apply { 4 duration = 1000 5 interpolator = AccelerateInterpolator() 6 } 7 //动画2 8 val fade1 = ObjectAnimator.ofFloat(binding.animatorTitle, "alpha", 1f, 0.5f).apply { 9 duration = 1000 10 } 11 //动画3 12 val fade2 = ObjectAnimator.ofFloat(binding.animatorTitle, "alpha", 0.5f, 1f).apply { 13 duration = 1000 14 } 15 16 AnimatorSet().apply { 17 /* 18 play(fade1) //顺序播放 19 play(fade2) 20 play(translation) 21 */ 22 play(fade1).before(fade2) //之前播放 23 play(translation).after(fade2) //在之后播放 24 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 25 val startColor = Color.parseColor("#000000") 26 val endColor = Color.parseColor("#ff0000") 27 val color = ObjectAnimator.ofArgb(binding.animatorTitle, "textColor", startColor,endColor).apply { 28 duration = 1000 29 interpolator = AccelerateDecelerateInterpolator() 30 } 31 play(translation).with(color) //同时播放 32 } 33 start() 34 }
5.4 Keyframe
1 private fun onKeyFrameClicked(v : View){ 2 val kf0 = Keyframe.ofFloat(0f, 0f) //第1帧 3 val kf1 = Keyframe.ofFloat(0.5f, 360f) //中间帧 4 val kf2 = Keyframe.ofFloat(1f, 0f) //最后1帧 5 val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2) 6 ObjectAnimator.ofPropertyValuesHolder(binding.animatorTitle, pvhRotation).apply { 7 duration = 5000 8 // interpolator = OvershootInterpolator() 9 // interpolator = CycleInterpolator(0.5f) 10 interpolator = AccelerateDecelerateInterpolator() 11 start() 12 } 13 }
6. 在资源文件中定义属性动画
- xml资源中使用的全部属性在:https://developer.android.com/guide/topics/resources/animation-resource#Property ,其中包含startOffset 等不常用属性。
- 从 Android 3.1 开始,属性动画的 XML 文件保存到
res/animator/
目录中,动画类与根元素对应关系如下表
ValueAnimator | <animator> |
ObjectAnimator | <objectAnimator> |
AnimatorSet | <set> |
6.1 animated-selector
animated-selector 要放在 v21 及更高的目录下,如:animator_btn_state.xml
1 <animated-selector 2 xmlns:android="http://schemas.android.com/apk/res/android"> 3 4 <!-- provide a different drawable for each state--> 5 <item android:id="@+id/pressed" android:drawable="@drawable/animator_btn_pressed" 6 android:state_pressed="true"/> 7 <item android:id="@+id/release" android:drawable="@drawable/animator_btn_normal" 8 android:state_pressed="false"/> 9 <item android:id="@+id/normal" 10 android:drawable="@drawable/btn_normal"/> 11 12 <!-- specify a transition --> 13 <transition android:fromId="@+id/pressed" android:toId="@+id/release"> 14 <animation-list> 15 <item android:duration="15" android:drawable="@drawable/animator_btn_pressed"/> 16 <item android:duration="15" android:drawable="@drawable/animator_btn_normal"/> 17 </animation-list> 18 </transition> 19 </animated-selector>
其中 id是自定义的。
button使用它
1 <Button 2 ... 3 android:stateListAnimator="@animator/animator_scale_state" 4 ... />
6.2 animator (translationX)
1 <?xml version="1.0" encoding="utf-8"?> 2 <animator xmlns:android="http://schemas.android.com/apk/res/android" 3 android:interpolator="@android:anim/linear_interpolator" 4 android:repeatCount="0" 5 android:repeatMode="restart" 6 android:duration="1000" 7 android:valueType="floatType" 8 android:valueTo="100f" />
解析后是 ValueAnimator 对象,代码中使用它:
1 private fun onXmlTranslation1AnimatorClicked(v : View){ 2 val update = object : ValueAnimator.AnimatorUpdateListener { 3 override fun onAnimationUpdate(animation: ValueAnimator?) { 4 val value = animation?.animatedValue as Float 5 Log.e("MainActivity","AnimatorUpdateListener value = $value") 6 binding.animatorTitle.translationX = value 7 } 8 } 9 (AnimatorInflater.loadAnimator(baseContext, R.animator.animator_translation1) as ValueAnimator).apply { 10 setTarget(binding.animatorView) 11 addUpdateListener(update) 12 start() 13 } 14 }
在资源中定义的ValueAnimator属性动画,需要updateListener且在更新回调中刷新view
6.3 objectAnimator (translationY)
1 <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 2 android:duration="1000" 3 android:propertyName="translationY" 4 android:interpolator="@android:anim/accelerate_decelerate_interpolator" 5 android:valueFrom="0" 6 android:valueTo="50f" />
解析后是 ObjectAnimator 对象,代码中使用它:
1 private fun onXmlTranslation2AnimatorClicked(v : View){ 2 (AnimatorInflater.loadAnimator(baseContext, R.animator.animator_translation2) as ValueAnimator).apply { 3 setTarget(binding.animatorView) 4 start() 5 } 6 }
6.4 AnimatorSet
1 <?xml version="1.0" encoding="utf-8"?> 2 <set android:ordering="sequentially" 3 xmlns:android="http://schemas.android.com/apk/res/android"> 4 <!--ordering 可选值 : together(默认) 同时播放 ,sequentially 顺序播放--> 5 <set> 6 <objectAnimator 7 android:propertyName="x" 8 android:duration="500" 9 android:valueTo="100" 10 android:valueFrom="0" 11 android:valueType="intType"/> 12 <objectAnimator 13 android:propertyName="y" 14 android:duration="500" 15 android:valueTo="100" 16 android:valueFrom="0" 17 android:valueType="intType"/> 18 </set> 19 <objectAnimator 20 android:propertyName="alpha" 21 android:duration="1000" 22 android:valueType="floatType" 23 android:valueFrom="0.1f" 24 android:valueTo="1f" 25 /> 26 </set>
解析后是AnimatorSet对象,在代码中使用示例如下:
1 private fun onXmlSetAnimatorClicked(v : View){ 2 (AnimatorInflater.loadAnimator(baseContext, R.animator.animator_set) as AnimatorSet).apply { 3 setTarget(binding.animatorTitle) 4 start() 5 } 6 7 }
6.5 alpha、rotation、scale
- alpha
1 <?xml version="1.0" encoding="utf-8"?> 2 <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 3 android:interpolator="@android:anim/linear_interpolator" 4 android:propertyName="alpha" 5 android:repeatCount="0" 6 android:repeatMode="restart" 7 android:duration="3000" 8 android:valueType="floatType" 9 android:valueFrom="0.1f" 10 android:valueTo="1f" />
- rotation
1 <?xml version="1.0" encoding="utf-8"?> 2 <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 3 android:propertyName="rotation" 4 android:repeatCount="0" 5 android:repeatMode="restart" 6 android:duration="6000" 7 android:valueFrom="0" 8 android:valueTo="360" />
- scale
1 <?xml version="1.0" encoding="utf-8"?> 2 <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 3 android:interpolator="@android:anim/accelerate_decelerate_interpolator" 4 android:propertyName="scaleY" 5 android:duration="5000" 6 android:valueTo="3f" 7 android:valueFrom="1" 8 android:repeatCount="-1" 9 android:repeatMode="restart" 10 />
代码中使用方法与6.3中类似。