ValueAnimator和ObjectAnimator除了使用ofInt()、ofFloat()、ofObject()定义动画之外,还可以使用ofPropertyValuesHolder()来定义动画。
1、PropertyValuesHolder
PropertyValuesHolder类的含义就是保存了所需操作的属性和对应的值,我们通过ofFloat()定义动画时,其内部也是将其封装成PropertyValuesHolder,然后通过操作PropertyValuesHolder来实现动画。
下面看下PropertyValuesHolder创建的常用函数
public static PropertyValuesHolder ofInt(String propertyName, int... values)
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)
1.1、PropertyValuesHolder 之ofInt()和ofFloat()
先来看下它们的构造函数
public static PropertyValuesHolder ofInt(String propertyName, int... values)
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
对比发现PropertyValuesHolder 的ofInt()和ofFloat()比ObjectAnimator的ofInt()、ofFloat()方法只少了一个Object target参数,其他参数完全一致,含义也完全一致。只需要注意:要操作的属性的值类型需要和接收的动画区间values的类型一致。并且当values只传入一个值时,就会调用getXXX()去获取属性的初始值作为区间的起始点。
在创建完PropertyValuesHolder 对象后就可以调用ObjectAnimator.ofPropertyValuesHolder()方法将其放置到ObjectAnimator中。
public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)
- target:需要执行动画的控件
- PropertyValuesHolder... values:可变长度参数,可以传入多个PropertyValuesHolder,每一个PropertyValuesHolder对应一种属性的动画,所以可以实现多个属性同时执行动画的功能。
示例:实现TextView旋转、平移和缩放
val propertyScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.1f)
val propertyScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.1f)
val propertyAlpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.2f)
val propertyTranslate = PropertyValuesHolder.ofFloat("translationX", 0f, 300f)
val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
tv_anim,
propertyAlpha,
propertyScaleX,
propertyScaleY,
propertyTranslate
)
objectAnimator.duration = 3000
objectAnimator.start()
1.2、PropertyValuesHolder 之ofObject()
看下它的构造函数
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)
ofObject()中的参数和ObjectAnimator.ofObject()中参数除了缺少一个target,其他参数完全一致。
示例:TextView实现字母从A变到Z
系统提供的Evaluator并不能满足,这里需要自定义一个TypeEvaluator
class CharEvaluator : TypeEvaluator<Char> {
override fun evaluate(fraction: Float, startValue: Char?, endValue: Char?): Char {
if (startValue == null || endValue == null)
return 'A'
return startValue + ((endValue - startValue) * fraction).toInt()
}
}
TypeEvaluator是用于计算当前时间点对应的进度值,值的类型为Char类型,而TextView的setText(CharSequence text)中接收的类型为CharSequence,所以需要在TextView中定义一个setXXX()方法。
class CharTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
fun setCharText(content: Char?) {
text = content?.toString()?:""
}
}
注意:setCharText()接收的参数需要是可为空类型的,否则在动画执行时找不到对应的setXXX()方法。
然后就可以使用ObjectAnimator.ofPropertyValuesHolder()构建动画了
val charProperty=PropertyValuesHolder.ofObject("charText",CharEvaluator(),'Z')
val animator=ObjectAnimator.ofPropertyValuesHolder(tv_anim,charProperty)
animator.duration=2000
animator.start()
2、KeyFrame
如果想修改动画的变化速率,可以通过自定义插值器也可以通过定义Evaluator,可是这两种方式都需要数学知识,比较麻烦。为此Google提供了简便的方式:使用KeyFrame来控制动画的速率。
KeyFrame的常用方法如下:
public static Keyframe ofInt(float fraction, int value)
public static Keyframe ofFloat(float fraction, float value)
public static Keyframe ofObject(float fraction, Object value)
public static Keyframe ofInt(float fraction)
public static Keyframe ofFloat(float fraction)
public static Keyframe ofObject(float fraction)
- fraction:表示当前动画的进度值,即插值器中返回的值
- value:表示当前动画所在的数值位置
比如Keyframe.ofFloat(0,0)表示动画进度为0时,动画所在的数值位置为0;KeyFrame.ofFloat(0.25f,-20f),表示动画进度为25%时,动画所在的数值位置为-20。KeyFrame(1.0f,0f),表示动画结束时,动画所在的数值位置为0。
接着我们可以使用PropertyValuesHolder.ofKeyFrame()方法,将KeyFrame放置进去了
public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values)
- property:动画要操作的属性
- values:根据设置的关键帧,定时的将指定的值输出给动画。
2.1、ofFloat()和ofInt()
示例:通过模拟电话响铃动画来看下KeyFrame的ofFloat()使用
val frame1 = Keyframe.ofFloat(0f, 0f)
val frame2 = Keyframe.ofFloat(0.1f, -20f)
val frame3 = Keyframe.ofFloat(0.2f, 20f)
val frame4 = Keyframe.ofFloat(0.3f, -20f)
val frame5 = Keyframe.ofFloat(0.4f, 20f)
val frame6 = Keyframe.ofFloat(0.5f, -20f)
val frame7 = Keyframe.ofFloat(0.6f, 20f)
val frame8 = Keyframe.ofFloat(0.7f, -20f)
val frame9 = Keyframe.ofFloat(0.8f, 20f)
val frame10 = Keyframe.ofFloat(0.9f, -20f)
val frame11 = Keyframe.ofFloat(1.0f, 0f)
val propertyValue = PropertyValuesHolder.ofKeyframe(
"rotation",
frame1,
frame2,
frame3,
frame4,
frame5,
frame6,
frame7,
frame8,
frame9,
frame10,
frame11
)
val animator=ObjectAnimator.ofPropertyValuesHolder(iv_phone,propertyValue)
animator.duration=1000
animator.start()
上面我们演示了ofFloat(float fraction, float value)的使用,下面我们看下ofFloat(float fraction),它比较特殊,只接收一个动画进度的参数,动画进度对应的数值还需要通过KeyFrame().setValue(Object value)进行设置。ofInt()用法和ofFloat()用法一致,不再赘述。
2.2、插值器设置
KeyFrame也允许设置插值器
public void setInterpolator(TimeInterpolator interpolator)
使用如下
val frame1 = Keyframe.ofFloat(0f, 0f)
val frame2 = Keyframe.ofFloat(0.1f, -20f)
frame2.interpolator = LinearInterpolator()
frame2设置了插值器,则表示关键帧frame1到关键帧frame2中间值计算就是使用线性插值器。
如果给frame1设置插值器,是无效的,因为frame1是第一帧。前面并没有其他帧。
2.3、ofObject()
public static Keyframe ofObject(float fraction)
public static Keyframe ofObject(float fraction, Object value)
依然通过实现TextView从A变到Z来解释下ofObjet()的使用
val frame1 = Keyframe.ofObject(0f, 'A')
val frame2 = Keyframe.ofObject(0.1f, 'L')
val frame3 = Keyframe.ofObject(1.0f, 'Z')
val propertyValue = PropertyValuesHolder.ofKeyframe(
"CharText",
frame1,
frame2,
frame3,
)
propertyValue.setEvaluator(CharEvaluator())
val animator = ObjectAnimator.ofPropertyValuesHolder(tv_anim, propertyValue)
animator.duration = 1000
animator.start()
在KeyFrame使用ofObject()构建关键帧时,就需要显式的设置Evaluator,因为动画过程中的中间值的类型,系统并不知道。
2.4、如果去掉第一帧和最后一帧会怎么样
看下面的例子
val frame1 = Keyframe.ofObject(0f, 'A')
val frame2 = Keyframe.ofObject(0.1f, 'L')
val frame3 = Keyframe.ofObject(1.0f, 'Z')
val propertyValue = PropertyValuesHolder.ofKeyframe(
"CharText",
frame1,
frame2,
frame3,
)
propertyValue.setEvaluator(CharEvaluator())
val animator = ObjectAnimator.ofPropertyValuesHolder(tv_anim, propertyValue)
animator.duration = 1000
animator.start()
上面动画实现的是A->Z的变化,如果去掉第一帧frame1,就会把frame2作为第一帧,动画就会从L->Z进行变化;如果去掉最后一帧frame3,则就会把frame2作为最后一帧,动画就会从A->L进行变化;如果同时去掉第一帧和最后一帧,目前只剩一帧,运行时会报错,因为使用KeyFrame构建动画时,要求至少有两帧。
3、ViewPropertyAnimator
先来回顾下ObjectAnimator的使用
val objectAnimator = ObjectAnimator.ofFloat(iv_phone, "alpha", 1.0f, 0.3f)
objectAnimator.setDuration(1000)
objectAnimator.start()
使用起来还是很复杂的,下面看下ViewPropertyAnimator的使用
iv_phone.animate().alpha(0.3f)
可以看到使用起来还是非常简单的。
使用ViewPropertyAnimator有以下几个点需要注意:
- animate():动画是从animate()方法开始,animate()返回一个ViewPropertyAnimator对象,可以通过这个对象设置需要实现动画的属性。
- 自动开始:上面我们并没有调用start()函数,但是动画还是正常的运行了。ViewPropertyAnimator会在下一次屏幕刷新的时候,自动开始运行动画。
3.1、常用函数
- alpha(tloat value) 设置透明度
- scaleY(tloat value) 设置 轴方向的缩放大小
- scaleX(tloat va lue) 设置 方向的缩放大
- translation Y(tloat value) 设置 轴方向的移动值
- translationX(tloat value) 设置 轴方 移动值
- rotation(tloat va lue) 设置绕 轴旋转度数
- rotationX (float value) 设置绕 轴旋转度数
- rotationY(tloat value] 设置绕 轴旋转度数
- x(tloat value) 相对于父容器的左上角坐标在 轴方向的最终位置
y(tloat value) 相对于父容器的左上角坐标在 轴方向的最终位置 - alphaBy(tloat value) 设置透明度增盘
- rotationBy(tloat value) 设置绕 轴旋转增盆
- rotationXBy(tloat value) 设置绕 轴旋转增盘
- rotationYBy(tloat value) 设置绕 轴旋转增盘
- tralislationXBy(tloat value) 设置 轴方向的移动值增量
- translationYBy(tloat value) 设置 轴方向的移动值增量
- scaleXBy(tloat value) 设置 轴方向的缩放大小增
- scaleYBy(tloat value) 设置 轴方向的缩放大小增量
- xBy(tloat va lue) 相对于父容器的左上角坐标在 轴方向的位置增
- yBy(tloat value) 相对于父容器 左上角坐标在 轴方向的位置增
- setlnterpolator(Timelnterpolator interpolator) 设置插值器
- setStartDelay(long startDelay) 设置开始延时
- setDuration(long duration) 设置动画时长
3.2、设置监听器
iv_phone.animate().alpha(1.0f).alpha(0.3f).setListener(
object:Animator.AnimatorListener{
override fun onAnimationStart(animation: Animator?) {
TODO("Not yet implemented")
}
override fun onAnimationEnd(animation: Animator?) {
TODO("Not yet implemented")
}
override fun onAnimationCancel(animation: Animator?) {
TODO("Not yet implemented")
}
override fun onAnimationRepeat(animation: Animator?) {
TODO("Not yet implemented")
}
}
)
3.3、性能考量
ObjectAnimator在动画过程中会通过反射或JNI技术实现控件属性的修改,而ViewPropertyAnimator会根据即将执行的动画预设控件的属性,在下一次屏幕刷新执行View的invalidate()时,统一刷新控件。从而解决了ObjectAnimator对每一个属性的单独计算、单独重绘的问题,(对多个属性进行动画时,需要多次invalidate()),在组合动画的场景下,ViewPropertyAnimator的性能更高。
但是并不是说ViewPropertyAnimator可以完全替代ObjectAnimator,ObjectAnimator可以对任何控件或对象做动画,但是当对View的多个非扩展属性做动画时,使用ViewPropertyAnimator还是很方便的。
4、给ViewGroup内的组件添加动画
上面我们讲述的动画都是针对单个控件的,如果我们想对ViewGroup内的组件做统一的入场动画或出场动画,使用ValueAnimator、ObjectAnimator或AnimatorSet是无法实现的。
4.1、animateLayoutChanges属性
功能如下:界面有增加和删除功能,点击增加按钮,向ViewGroup中增加一个按钮,点击删除按钮,删除一个按钮,布局文件如下:
给ViewGroup设置animateLayoutChanges=true
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_add"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@android:color/darker_gray"
android:text="添加" />
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:background="@android:color/darker_gray"
android:text="删除" />
</LinearLayout>
<LinearLayout
android:id="@+id/view_contanier"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
实现按钮的增加和删除
val layoutParams = LinearLayout.LayoutParams(400, 150)
btn_add.setOnClickListener {
val button = Button(this)
button.layoutParams = layoutParams
count++
button.text = "按钮$count"
view_contanier.addView(button,0)
}
btn_delete.setOnClickListener {
if (view_contanier.childCount > 0)
view_contanier.removeView(view_contanier[0])
}
运行发现:在增加和删除时,系统会默认加入透明度和平移的动画。这个动画是默认的,无法指定的。
4.2、LayoutTransition
LayoutTransition可以实现在向ViewGroup中添加和删除控件时,自定义动画的实现。
实现步骤如下:
- 1、创建LayoutTransition
val transition=LayoutTransition()
- 2、创建属性动画
val animOut=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f)
transition.setAnimator(APPEARING,animOut)
setAnimator()方法接收两个参数
public void setAnimator(int transitionType, Animator animator)
transitionType可选值有5个:
- APPEARING:元素在容器中出现时的所定义的动画
- DISAPPEARING:元素从容器中消失时所定义的动画
- CHANGE_APPEARING:容器中增加一个控件,其他控件变化的动画
- CHANGE_DISAPPEARING:容器中删除一个控件,其他控件变化的动画
- 3、给ViewGroup设置transition
view_contanier.layoutTransition=transition
setLayoutTransition()是ViewGroup类中的,所以所有派生自ViewGroup类的控件都可以调用该方法。
注意:transitionType为CHANGE_APPEARING或CHANGE_DISAPPEARING限制较多,而且不实用,不再赘述。
4.3、其他函数
- public void setDuration(long duration):设置动画时长
- public void setDuration(int transitionType, long duration):针对单个transitionType设置动画时长
- public void setInterpolator(int transitionType, TimeInterpolator interpolator):针对单个type设置插值器
- public void setStartDelay(int transitionType, long delay):针对单个type设置动画延时
- public void setStagger(int transitionType, long duration):针对单个 Type 置每个子 item 动画的 时间间隔
- public void addTransitionListener(TransitionListener listener):设置动画监听
public interface TransitionListener {
public void startTransition(LayoutTransition transition, ViewGroup container,View view, int transitionType);
public void endTransition(LayoutTransition transition, ViewGroup container,View view, int transitionType);
}
在任何类型的 LayoutTransition 开始和结束时,都会调用 TransitionListe ner的startTransition()和endTransition()在 TransitionListner 中有 4个参数
• LayoutTransition transition 当前的 LayoutTran ition 实例。
• ViewGroup container 当前应用 LayoutTransition 容器
• View view 当前在做动画 View 象。
• int transitionType 当前的 LayoutTransition 类型,取值有 APPEAING、 DISAPPEARING、CHANGE APPEARING 和 CHANGE ISAPPEARING
网友评论