Android属性动画笔记基础汇总

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也可以实现一些简单的组合动画什么的。

相关推荐
阿巴斯甜6 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker7 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95278 小时前
Andorid Google 登录接入文档
android
黄林晴9 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android