android 动画归纳

516 阅读8分钟

属性动画

随时间更改任何对象属性的动画,无论其是否绘制到屏幕上。属性动画会在指定时长内更改属性的值。

ValueAnimator

Animator子类,主要作用在给定时间给定插值器(默认为线下插值器LinearInterpolator)下计算值变化。主要获取方法ofInt()ofFloat()ofObject()

//线性变化0到100 
ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 200
    addUpdateListener {
        textView.translationX = it.animatedValue as Float
    }
    start()
}

xml创建,为了和视图动画区分属性动画的XML文件保存到 res/animator/

res\animator\value_animator.xml

<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:repeatCount="1"
    android:repeatMode="restart"
    android:valueFrom="0"
    android:valueTo="100"
    android:valueType="floatType" />

加载

(AnimatorInflater.loadAnimator(this, R.animator.value_animator) as ValueAnimator).apply {
    addUpdateListener {
        textView.translationX = it.animatedValue as Float
    }
    start()
}

ObjectAnimator

ValueAnimator的子类,添加了对目标对象命名属性动画的效果。主要获取方法ofInt()ofFloat()ofObject()。相比ValueAnimator多了目标对象和目标对象的属性。

    //更新translationX属性 
   ObjectAnimator.ofFloat(textView, "translationX", 0f,100f).apply {
        duration=200
        start()
    }

xml 创建

res\animator\object_animator.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:valueTo="100"
    android:valueFrom="0"
    android:valueType="floatType"
    android:propertyName="translationX"
    android:repeatCount="1"
    android:repeatMode="restart"/>

加载

 (AnimatorInflater.loadAnimator(this, R.animator.object_animator) as ObjectAnimator).apply {
        target = textView
        start()
    }

注意:对象属性必须具有 set<PropertyName>()形式的 setter 函数(采用驼峰式大小写形式)

AnimatorSet

主要作用为将多个Animator动画按顺序.一起播放或延迟播放。

直接添加
playSequentially按顺序播放一组Animator动画
playTogether同时播放一组Animator动画

    val annotation1 = ObjectAnimator.ofFloat(binding.textView, "translationX", 0f, 100f)
    val annotation2 = ObjectAnimator.ofFloat(binding.textView, "translationY", 0f, 100f)
    AnimatorSet().apply {
                //按顺序播放
                playSequentially(annotation1,annotation2)
                //同时播放
                //playTogether(annotation1,annotation2)
                start()
            }
    

使用AnimatorSet.Builder,用于构建动画的依赖关系,注意,只有先调用AnimatorSet#play(Animator),然后才能依赖关系主要方法

afterplay(Animator)添加动画依赖在此动画之后执行
beforeplay(Animator)添加动画依赖在此动画之前执行
with和play(Animator)添加动画一起执行

    val annotation1 = ObjectAnimator.ofFloat(binding.textView, "translationX", 0f, 100f)
    val annotation2 = ObjectAnimator.ofFloat(binding.textView, "translationY", 0f, 100f)
    val builder = AnimatorSet().apply {
        //annotation1在annotation2后面执行
        play(annotation1).after(annotation2)
    }
    AnimatorSet().apply {
        play(builder)
        start()
    }
    

TypeEvaluator(类型评估)

当需要为Android系统无法识别的类型添加动画效果,可以通过实现TypeEvaluator接口来创建您自己的评估程序。TypeEvaluator 接口中只有一种要实现的方法,可以查看系统中实现的类FloatEvaluator

 class FloatEvaluator : TypeEvaluator<Number> {
        override fun evaluate(
            fraction: Float, //从起始值到结束值的百分数
            startValue: Number,//开始值
            endValue: Number//结束值
        ): Float {
            val startFloat: Float = startValue.toFloat()
            return startFloat + fraction * (endValue.toFloat() - startFloat)
        }
    }

TimeInterpolator(插值器)

插值器指定了如何根据时间计算动画中的特定值。例如,您可以指定动画在整个动画中以线性方式播放,即动画在整个播放期间匀速移动;也可以指定动画使用非线性时间,例如动画在开始后加速并在结束前减速。

自定义差值器需要实现getInterpolation方法

 class  MyInterpolator : TimeInterpolator{
     override fun getInterpolation(input: Float): Float {
        //input为动画播放的百分比 插值器通过修改此值来调整动画速率
        return  input
     }
  }

Keyframe(关键帧)

Keyframe由时间和当前状态组成,动画在随时间由前一个关键帧进入下一个关键帧。每个关键帧还包含一个可选TimeInterpolator 对象,该对象定义了关键帧之前的时间间隔内的时间插值。代码实现

    val kf0 = Keyframe.ofFloat(0f, 0f)
    val kf1 = Keyframe.ofFloat(.5f, 360f)
    //关键帧kf1到kf2的插值器
    kf1.interpolator= OvershootInterpolator()
    val kf2 = Keyframe.ofFloat(1f, 0f)
    val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
    ObjectAnimator.ofPropertyValuesHolder(binding.textView, pvhRotation).apply {
        duration = 5000
        start()
    }

从API 23开始可使用xml实现res\animator\key_frame.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000">
    <propertyValuesHolder android:propertyName="rotation">
        <keyframe
            android:fraction="0"
            android:value="0"
            android:valueType="floatType"></keyframe>
        <keyframe
            android:fraction="0.5"
            android:value="360"
            android:interpolator="@android:anim/overshoot_interpolator"
            android:valueType="floatType"></keyframe>
        <keyframe
            android:fraction="1"
            android:value="0"
            android:valueType="floatType"></keyframe>
    </propertyValuesHolder>
</objectAnimator>

加载

 (AnimatorInflater.loadAnimator(this, R.animator.key_frame) as ObjectAnimator).apply {
        target = binding.textView
        start()
    }

xml和代码实现效果相同

ViewGroup布局更改动画

ViewGroup布局更改动画主要通过 LayoutTransition 类来实现。当向ViewGroup中添加或者移除视图时,或使用 VISIBLEINVISIBLEGONE 调用视图的 setVisibility() 方法时。都可定义ViewGroup中view的变化动画。主要定义LayoutTransition类并调用LayoutTransitionsetAnimator()方法传入LayoutTransition 常量和 Animator对象。然后将LayoutTransition设置到ViewGroup中。LayoutTransition 常量主要为:

  • APPEARING:ViewGroup中view的显示动画
  • CHANGE_APPEARING:ViewGroup中view显示时,相连view移动动画
  • DISAPPEARINGViewGroup中隐藏view时,view的消失动画
  • CHANGE_DISAPPEARINGViewGroup中隐藏view时,相连view移动动画
    //定义动画
    val animator = ObjectAnimator.ofFloat(Any(), "rotation", 0f, 360f)
    val layoutTransition= LayoutTransition()
    //ViewGroup中子view显示时的动画
    layoutTransition.setAnimator(LayoutTransition.APPEARING,animator)
    binding.rootLayout.layoutTransition= layoutTransition

使用默认的ViewGroup布局更改动画可在布局文件中添加android:animateLayoutChanges="true"

 <LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

StateListAnimator

视图状态更改动画,可以给view在不同状态下设置动画(如按下、聚焦)。示例res\xml\state_animator.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <set>
            <!--     按下时放打大1.5倍      -->
            <objectAnimator android:propertyName="scaleX"
                android:duration="100"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="100"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <item android:state_pressed="false">
        <set>
            <!--     取消按压时还原      -->
            <objectAnimator android:propertyName="scaleX"
                android:duration="100"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="100"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

直接布局中使用

  <Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    android:stateListAnimator="@xml/state_animator"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

代码加载

 val stateListAnimator = AnimatorInflater.loadStateListAnimator(this, R.xml.state_animator)
 binding.button.stateListAnimator = stateListAnimator

效果

ViewPropertyAnimator

ViewPropertyAnimator使用单个Animator对象为view多个属性并行添加动画。 ObjectAnimator 非常相似但相比更加高效简洁。使用多个 ObjectAnimator 对象、使用单个 ObjectAnimator 对象以及使用 ViewPropertyAnimator 的区别。

多个 ObjectAnimator 对象

    val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
    val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
    AnimatorSet().apply {
        playTogether(animX, animY)
        start()
    }

一个 ObjectAnimator

    val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
    val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
    ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()

ViewPropertyAnimator

  myView.animate().x(50f).y(100f)

AnimationDrawable(帧动画)

将多个连续图片组成的动画,示例 res\drawable\rocket_thrust.xml

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="true">
        <!--帧动画图片和持续时间-->
        <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
        <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
        <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
    </animation-list>

代码中使用

    val rocketImage = findViewById<ImageView>(R.id.rocket_image).apply {
        //将帧动画设置到ImageView中
        setBackgroundResource(R.drawable.rocket_thrust)
        //获取AnimationDrawable对象
        rocketAnimation = background as AnimationDrawable
        }
    //开始动画
    rocketImage.setOnClickListener({ rocketAnimation.start() })

AnimatedVectorDrawable(矢量动画)

为矢量图添加动画,如旋转,路径变换等。首先我们需要给需要动画的grouppath添加android:name 属性,以便在Animator引用。VectorDrawablegrouppath定义的属性:

定义res/drawable/vectordrawable.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="64dp"
    android:width="64dp"
    android:viewportHeight="600"
    android:viewportWidth="600">
    <!--定义 android:name属性 在后面Animator 中引用-->
    <group
        android:name="rotationGroup"
        android:pivotX="300.0"
        android:pivotY="300.0"
        android:rotation="45.0" >
        <path
            android:name="v"
            android:fillColor="#000000"
            android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
        </group>
    </vector>

创建矢量动画AnimatedVectorDrawable的XML res/drawable/animatorvectordrawable.xml

    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/vectordrawable" >
            <!--将rotationGroup组设置rotation动画-->
            <target
                android:name="rotationGroup"
                android:animation="@animator/rotation" />
            <!--将路径设置path_morph动画-->  
            <target
                android:name="v"
                android:animation="@animator/path_morph" />
    </animated-vector>

使用ObjectAnimator或AnimatorSet定义的动画XML

使用ObjectAnimator将目标组旋转 360 度res/animator/rotation.xml

   <objectAnimator
        android:duration="6000"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="360" />

使用AnimatorSet将矢量可绘制对象的路径从一个形状变为另一个形状 res/animator/path_morph.xml

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <objectAnimator
            android:duration="3000"
            android:propertyName="pathData"
            android:valueFrom="M300,70 l 0,-70 70,70 0,0   -70,70z"
            android:valueTo="M300,70 l 0,-70 70,0  0,140 -70,0 z"
            android:valueType="pathType" />
    </set>

使用

    //设置背景
    binding.imageView.setImageResource(R.drawable.animatorvectordrawable)
    //获取矢量动画
    val animatedVectorDrawable=  binding.imageView.drawable as AnimatedVectorDrawable
    binding.imageView.setOnClickListener {
        //开始动画
        animatedVectorDrawable.start()
    }

效果

DynamicAnimation(动力动画)

物理的动画的基础类,引用

   implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0'

FlingAnimation(惯性动画)

一种持续初始动量(通常是手势速度)并且逐渐变慢的动画

    val flingAnim = FlingAnimation(
        view,
        //View的translationX属性。
        DynamicAnimation.TRANSLATION_X
    )
        //将开始速度设置为-2000(像素/秒)
        .setStartVelocity(-2000f)
        //可选,但建议为动画设置合理的最小和最大范围。
        //在这种情况下,我们将指向和重新分别设置为-200和2000。
        .setMinValue(-200f).setMaxValue(2000f)
        //开始动画
        flingAnim.start()

SpringAnimation(弹性动画)

使用

    //创建动画以动画化视图的X属性,设置
    //将会将弹簧弹回0,并以5000(像素/ s)的开始速度开始动画
    val anim = SpringAnimation(view, DynamicAnimation.X, 0)
        .setStartVelocity(5000f)
        anim.start()

lottie(比较好的三方动画库)

Lottie是一个适用于Android,iOS,Web和Windows的库,它可以使用Bodymovin解析以json 格式导出的Adobe After Effects动画,并在移动设备和Web 上原生呈现它们!官网地址:airbnb.io/lottie