1. 简介
在引入
属性动画
之前,Android已经有了
Tween animation(补间动画)
。引入新的Animator的原因是因为补间动画有很多局限性。
补间动画的分类:
AlphaAnimation
ScaleAnimation
TranslateAnimation
RotateAnimation
AnimationSet
补间动画的局限性:
为了补充和解决上述补间动画的不足,所以在Android3的时候引入了
ValueAnimator
和
ObjectAnimator
。
2. 属性动画
2.1 属性动画的结成结构
2.2 定义属性动画方法
跟补间动画一样,可以使用Xml和Code的方式创建动画。
在animation文件夹中创建xml动画文件。(如果没有animation文件夹,需要手动创建)
<objectAnimator
android:duration="int"
android:interpolator="@[package:]anim/interpolator_resource"
android:propertyName="string"
android:valueType=["intType" | "floatType"]
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
//1. 在animation文件夹中创建xml文件
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="360"
android:startOffset="0"
android:repeatCount="infinite"
android:repeatMode="reverse"
//2. 在代码中导入动画xml
val objectAnimator = AnimatorInflater.loadAnimator(this, R.animator.object_animator) as ObjectAnimator
objectAnimator.setTarget(target)
objectAnimator.start()
复制代码
代码的创建方式在下面介绍。
3. ValueAnimator
ValueAnimator
正如其名字,它是关于数字的计算动画。它不会与View直接交互,而是通过ValueAnimator#addUpdateListener
来设置动画效果。比如在TextView上显示从0到50时,就可以用ValueAnimator
。
3.1 ValueAnimator的创建
在代码中创建ValueAnimator
。
// 创建Animator,同时传入取值范围
val animator = ValueAnimator.ofFloat(0F, 5000F)
// 设置差值器, 这里选择的是线性差值器
animator.interpolator = LinearInterpolator()
// 设置动画执行时间
animator.setDuration(2000)
复制代码
如果不设置interpolator,则默认使用的是AccelerateDecelerateInterpolator()
。如果设置的是null,则会默认使用LinearInterpolator()
。
3.2 添加UpdateListener
在ValueAnimator中有数值变化时,会调用onAnimationUpdate
接口。所以我们需要实现这个Listener。
animator.addUpdateListener {
// 在TextView中更新text
binding.textView.text = ((it.animatedValue as Float).toInt() / 100).toString()
// 把值传入自定义贝塞尔View,是其产生动画效果
binding.bezierView.setValue(it.animatedValue as Float)
复制代码
自定义贝塞尔View的源码如下。
关于贝塞尔曲线,可以参考我的另一篇文章。
class BezierView : View {
private var path: Path = Path()
private lateinit var paint: Paint = Paint()
private var h: Int = 0
private var w: Int = 0
private var controlPoint1: PointF = PointF()
private var controlPoint2: PointF = PointF()
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// 设置当前view的高和宽
this.h = h
this.w = w
controlPoint1 = PointF(this.w.toFloat() / 4, 0F)
controlPoint2 = PointF(this.w.toFloat() / 4 * 3, this.h.toFloat())
fun setValue(degree: Float) {
val controlY = degree / 5000 * h
controlPoint1 = PointF(this.w.toFloat() / 4, controlY)
controlPoint2 = PointF(this.w.toFloat() / 4 * 3, this.h.toFloat() - controlY)
invalidate()
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// 重置path, 为的是防止重复绘制贝塞尔曲线,使画布上残留多条曲线
path.reset()
// 配置画笔paint
paint.color = context.getColor(R.color.colorAccent)
paint.strokeWidth = 2F
paint.style = Paint.Style.STROKE
// 设置左右两个基准点
val pointLeft = PointF(0F, h / 2.toFloat())
val pointRight = PointF(w.toFloat(), h / 2.toFloat())
// 绘制左右基准点
canvas?.drawPoint(pointLeft.x, pointLeft.y, paint)
canvas?.drawPoint(pointRight.x, pointRight.y, paint)
paint.color = context.getColor(R.color.colorPrimaryDark)
// 为了绘制贝塞尔曲线,需要移动到其中一个基准点
path.moveTo(pointLeft.x, pointLeft.y)
// 根据基准点和控制点,绘制贝塞尔曲线
path.cubicTo(
controlPoint1.x,
controlPoint1.y,
controlPoint2.x,
controlPoint2.y,
pointRight.x,
pointRight.y
// 在画布上画path
canvas?.drawPath(path, paint)
复制代码
3.3 播放动画
最后需要在合适的节点开始播放动画。
// 播放动画
animator.start()
// 动画暂定
animator.pause()
// 播放结束
animator.end()
4. ObjectAnimator
跟ValueAnimator
一样的方式创建ObjectAnimator
。
// 创建动画,
val objectAnimator = ObjectAnimator.ofFloat(binding.imageView, "rotation", 0F, 360F, 0F)
// 设置动画执行时间
objectAnimator.setDuration(2000)
// 开始播放
objectAnimator.start()
复制代码
创建动画时的第一个传参是被动画对象View,第二个是properyName
,第三个是变长参数的起始数值。
其中propertyName
的类型一共有以下几种:
5.1 AnimatorListener
AnimatorListener
的监听器主要监听的是属性动画的开始,结束,取消,重复。
public static interface AnimatorListener{
void onAnimationStart(Animator animation, boolean isReverse) {}
void onAnimationEnd(Animator animation, boolean isReverse) {}
void onAnimationCancel(Animator animation, boolean isReverse) {}
void onAnimationRepeat(Animator animation, boolean isReverse) {}
复制代码
5.2 AnimatorPauseListener
AnimatorPauseListener
的监听器主要监听的是属性动画的暂停,恢复状态。
public static interface AnimatorPauseListener {
void onAnimationPause(Animator animation);
void onAnimationResume(Animator animation);
复制代码
5.3 AnimatorUpdateListener
AnimatorUpdateListener
的监听器主要监听的是属性动画中值得变化。
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
复制代码
6. 插值器(Interpolator)
6.1 插值器类型
属性动画的Interpolator
和补间动画的的是一样的。
插值器一共有一下几种:
如果上面的插值器不符合要求可以自定义一个新的插值器。
自定义插值器时需要继承BaseInterpolator
。
重写public float getInterpolation(float input)
。
线性插值器是如下的代码,可以作为参考。
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
public LinearInterpolator(Context context, AttributeSet attrs) {
public float getInterpolation(float input) {
return input;
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
复制代码
7. 自定义估值器(TypeEvaluator)
估值器是用于计算ValueAnimator
中的数值变化。ValueAnimator
的默认的估值器是线性的。
如果想要自定义估值器需要继承TypeEvaluator
,以及重写public T evaluate(float fraction, T startValue, T endValue);
。
示例如下:
class CustomTypeEvaluator : TypeEvaluator<Float> {
override fun evaluate(fraction: Float, startValue: Float?, endValue: Float?): Float {
return fraction * abs(endValue ?: 0F - startValue!!)
复制代码
8. Github
本文的示例: github.com/HyejeanMOON…
其他教程:
Google的MergeAdapter的使用: juejin.cn/post/684490…
Paging在Android中的应用: juejin.cn/post/684490…
Android UI测试之Espresso: juejin.cn/post/684490…
Android ConstraintLayout的易懂教程: juejin.cn/post/684490…
在RecyclerView中可以应对多个ViewType的库--Groupie: juejin.cn/post/684490…