为什么要引入属性动画? 视图动画分为补间动画以及逐帧动画,补间动画能够实现空间的渐隐渐现、移动、缩放和旋转,逐帧动画可是顺序播放一组图片从而实现动画效果。那为什么还需要引入属性动画呢?
比如,我们需要将一个控件的背景在1分钟之内由红色变为绿色,这个效果是没有办法通过视图动画来实现的,但是可以通过属性动画来完美的实现,因为属性动画是通过改变控件的属性来实现动画效果的。
另一个原因是补间动画的点击区域问题,如果我们使用补间动画移动了一个控件,之前绑定在该控件上的点击事件无法跟随控件移动,还停留在控件之前的位置。
ValueAnimator 的入门实例。
val valueAnimator:ValueAnimator = ValueAnimator.ofInt(0,300)
valueAnimator.setDuration(5000)
valueAnimator.addUpdateListener {
val currentValue:Int = it.animatedValue as Int
Log.d(TAG, "start: currentValue = ${currentValue}")
imageView.layout(currentValue,
currentValue,
currentValue + imageView.width,
currentValue + imageView.height)
}
valueAnimator.start()
我们定义了一个ValueAnimator,并为其设置了一个监听器,监听从0到300的值,在监听回调函数中,改变imageView的坐标,已达到移动ImageView的效果。
由此可见,ValueAnimator负责对指定的区域进行计算,我们通过对运算过程的监听,对控件进行属性上的更改,已达到动画效果。
下面介绍在ValueAnimator中的一些常用的函数。
1、ofInt和ofFloat函数
在上面的例子中,我们使用了ofInt函数,他们的参数类型都是可变量参数类型,我们可以传入任意数量的值,传入的值列表就表示动画时值的变化范围。比如
val valueAnimator = ValueAnimator.ofFloat(0f,500f,200f,400f)
valueAnimator.setDuration(5000)
valueAnimator.addUpdateListener {
val currentValue:Int = (it.animatedValue as Float).toInt()
Log.d(TAG, "start: currentValue = ${currentValue}")
imageView.layout(currentValue,
currentValue,
currentValue + imageView.width,
currentValue + imageView.height)
}
valueAnimator.start()
可实现如下的效果
在这个例子中,动画开始时,值从0变化到500,然后从500变化到200,最后从200变化到400。所以传入的参数越多,动画就越复杂。
练习,创建一个自定义的View,View上下跳动,每一次跳动更换一张图片
先看效果图
原理分析:
val valueAnimator = ValueAnimator.ofInt(0,200,0)
上面的属性动画可以让我们的View上下跳动,并且可以通过设置属性动画的重复次数以及重复模式,可以让View一直上下跳动。
valueAnimator.repeatMode = ValueAnimator.RESTART
valueAnimator.repeatCount = ValueAnimator.INFINITE
通过监听动画何时重启,可以替换不同的图片,完成的代码如下
package com.example.animation
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.appcompat.widget.AppCompatImageView
/**
* 项目名称 AndroidViewBook
* 创建时间 2022/1/16 6:55 下午
*/
class LoadingImageView : AppCompatImageView {
private var mTop = 0
private var mCurrentIndex = 0
private val maxCount = 3
constructor(context: Context?) : super(context!!) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(
context!!, attrs
) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context!!, attrs, defStyleAttr
) {
init()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
mTop = top
}
fun init() {
val valueAnimator = ValueAnimator.ofInt(0, 200, 0)
valueAnimator.repeatMode = ValueAnimator.RESTART
valueAnimator.repeatCount = ValueAnimator.INFINITE
valueAnimator.duration = 2000
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
valueAnimator.addUpdateListener {
val value = (it.animatedValue as Int)
top = mTop - value
}
valueAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
setImageResource(R.drawable.one)
}
override fun onAnimationEnd(animation: Animator?) {
TODO("Not yet implemented")
}
override fun onAnimationCancel(animation: Animator?) {
TODO("Not yet implemented")
}
override fun onAnimationRepeat(animation: Animator?) {
mCurrentIndex++
when (mCurrentIndex % maxCount) {
0 -> setImageResource(R.drawable.one)
1 -> setImageResource(R.drawable.five)
2 -> setImageResource(R.drawable.six)
}
}
})
valueAnimator.start()
}
}
参考资料《Android自定义控件开发与实战》