【Android】属性动画

在属性动画出现之前,Android 系统提供的动画只有帧动画和 View 动画。View 动画我们都了解,它提供了 AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation 这4种动画方式,并提供了 AnimationSet 动画集合来混合使用多种动画。随着属性动画的推出,View 动画不再风光。

相比属性动画,View 动画一个非常大的缺陷突显,其不具有交互性。当某个元素发生 View 动画后,其响应事件的位置依然在动画进行前的地方,所以 View 动画只能做普通的动画效果,要避免涉及交互操作。但是它的优点也非常明显:效率比较高,使用也方便。由于之前已有的动画框架 Animation 存在一些局限性,也就是动画改变的只是显示,但 View 的位置没有发生变化,View 移动后并不能响应事件,所以谷歌推出了新的动画框架,帮助开发者实现更加丰富的动画效果。在 Animator 框架中使用最多的就是 AnimatorSet 和 ObjectAnimator,配合使用 ObjectAnimator 进行更精细化的控制,控制一个对象和一个属性值,而使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。属性动画通过调用属性 get、set 方法来真实地控制一个 View 的属性值,因此,强大的属性动画框架基本可以实现所有的动画效果。

一、ObjectAnimator

ObjectAnimator 是属性动画最重要的类,创建一个 ObjectAnimator 只需通过其静态工厂类直接返还一个 ObjectAnimator 对象。参数包括一个对象和对象的属性名字,但这个属性必须有 get 和 set 方法,其内部会通过 Java 反射机制来调用 set 方法修改对象的属性值。下面看看平移动画是如何实现的,代码如下所示:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/test_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val objectAnimator = ObjectAnimator.ofFloat(testView, "translationX", 200F)
        objectAnimator.setDuration(3000)
        objectAnimator.start()
    }
}

运行程序,效果如图1所示:

图1

通过 ObjectAnimator 的静态方法,创建一个 ObjectAnimator 对象,查看 ObjectAnimator 的静态方法 ofFloat(),源码如下所示:

java 复制代码
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}

从源码可以看出第一个参数是要操作的 Object;第二个参数是要操作的属性;最后一个参数是一个可变的 float 类型数组,需要传进去该属性变化的取值过程,这里设置了一个参数,变化到200。与 View 动画一样,也可以给属性动画设置显示时长、插值器等属性。下面就是一些常用的可以直接使用的属性动画的属性值。

  • translationX 和 translationY:用来沿着 X 轴或者 Y 轴进行平移。
  • rotation、rotationX、rotationY:用来围绕 View 的支点进行旋转。
  • PrivotX 和 PrivotY:控制 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认该支点位置就是 View 对象的中心点。
  • alpha:透明度,默认是1(不透明),0代表完全透明。
  • x 和 y:描述 View 对象在其容器中的最终位置。

需要注意的是,在使用 ObjectAnimator 的时候,要操作的属性必须要有 get 和 set 方法,不然 ObjectAnimator 就无法生效。如果一个属性没有 get、set 方法,也可以通过自定义一个属性类或包装类来间接地给这个属性增加 get 和 set 方法。现在来看看如何通过包装类的方法给一个属性增加 get 和 set 方法,代码如下所示:

kotlin 复制代码
class MyView(private val view: View) {

    fun getWidth(): Int {
        return view.layoutParams.width
    }

    fun setWidth(width: Int) {
        view.layoutParams.width = width
        view.requestLayout()
    }
}

使用时只需要操作包类就可以调用 get、set 方法了:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val myView = MyView(testView)
        ObjectAnimator.ofInt(myView, "width", 500).setDuration(3000).start()
    }
}

运行程序,效果如图2所示:

图2

二、ValueAnimator

ValueAnimator 不提供任何动画效果,它更像一个数值发生器,用来产生有一定规律的数字,从而让调用者控制动画的实现过程。通常情况下,在 ValueAnimator 的 AnimatorUpdateListener 中监听数值的变化,从而完成动画的变换,代码如下所示:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val valueAnimator = ValueAnimator.ofFloat(0F, 100F)
        valueAnimator.setTarget(testView)
        valueAnimator.setDuration(3000).start()
        valueAnimator.addUpdateListener { animation ->
            val animatedValue = animation.animatedValue
        }
    }
}

三、动画的监听

完整的动画具有 start、Repeat、End、Cancel 这4个过程,代码如下所示:

kotlin 复制代码
val animator = ObjectAnimator.ofFloat(testView, "alpha", 1.5F)
animator.addListener(object : AnimatorListener {
    override fun onAnimationStart(animation: Animator) {
    }

    override fun onAnimationEnd(animation: Animator) {
    }

    override fun onAnimationCancel(animation: Animator) {
    }

    override fun onAnimationRepeat(animation: Animator) {
    }
})

大部分时候我们只关心 onAnimationEnd 事件,Android 也提供了 AnimatorListenterAdaper 来让我们选择必要的事件进行监听。

kotlin 复制代码
val animator = ObjectAnimator.ofFloat(testView, "alpha", 1.5F)
animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationEnd(animation: Animator) {
        super.onAnimationEnd(animation)
    }
})

四、组合动画(AnimatorSet)

AnimatorSet 类提供了一个 play() 方法,如果我们向这个方法中传入一个 Animator 对象(ValueAnimator 或 ObjectAnimator),将会返回一个 AnimatorSet.Builder 的实例。AnimatorSet 的 play() 方法源码如下所示:

java 复制代码
public Builder play(Animator anim) {
    if (anim != null) {
        return new Builder(anim);
    }
    return null;
}

很明显,在 play() 方法中创建了一个 AnimatorSet.Builder 类,这个 Builder 类是 AnimatorSet 的内部类。我们来看看这个 Builder 类中有什么,代码如下所示:

java 复制代码
public class Builder {

    private Node mCurrentNode;

    Builder(Animator anim) {
        mDependencyDirty = true;
        mCurrentNode = getNodeForAnimation(anim);
    }

    public Builder with(Animator anim) {
        Node node = getNodeForAnimation(anim);
        mCurrentNode.addSibling(node);
        return this;
    }

    public Builder before(Animator anim) {
        Node node = getNodeForAnimation(anim);
        mCurrentNode.addChild(node);
        return this;
    }

    public Builder after(Animator anim) {
        Node node = getNodeForAnimation(anim);
        mCurrentNode.addParent(node);
        return this;
    }

    public Builder after(long delay) {
        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
        anim.setDuration(delay);
        after(anim);
        return this;
    }
}

从源码中可以看出,Builder 类采用了建造者模式,每次调用方法时都返回 Builder 自身用于继续构建。AnimatorSet.Builder 中包括以下4个方法:

  • with(Animator anim):将现有动画和传入的动画同时执行。
  • before(Animator anim):将现有动画插入到传入的动画之前执行。
  • after(Animator anim):将现有动画插入到传入的动画之后执行。
  • after(long delay):将现有动画延迟指定毫秒后执行。

AnimatorSet 正是通过这几种方法来控制动画播放顺序的。这里再举一个例子,代码如下所示:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val animator1 = ObjectAnimator.ofFloat(testView, "translationX", 0.0F, 200.0F, 0F)
        val animator2 = ObjectAnimator.ofFloat(testView, "scaleX", 1.0F, 2.0F)
        val animator3 = ObjectAnimator.ofFloat(testView, "rotationX", 0.0F, 90.0F, 0.0F)
        val set = AnimatorSet()
        set.setDuration(3000)
        set.play(animator1).with(animator2).after(animator3)
        set.start()
    }
}

首先我们创建3个 ObjectAnimator,分别是 animator1、animator2 和 animator3,然后创建 AnimatorSet。在这里先执行 animator3,然后同时执行 animator1 和 animator2(也可以调用 set.playTogether(animator1,animator2) 来使这两种动画同时执行)。

运行程序,效果如图3所示:

图3

五、组合动画(PropertyValuesHolder)

除了上面的 AnimatorSet 类,还可以使用 PropertyValuesHolder 类来实现组合动画。不过这个组合动画就没有上面的丰富了,使用 PropertyValuesHolder 类只能是多个动画一起执行。当然我们得结合 ObjectAnimator.ofPropertyValuesHolder(Object target,PropertyValuesHolder...values) 方法来使用。其第一个参数是动画的目标对象;之后的参数是 PropertyValuesHolder 类的实例,可以有多个这样的实例。具体代码如下所示:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0F, 1.5F)
        val valuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", 0.0F, 90.0F, 0.0F)
        val valuesHolder3 = PropertyValuesHolder.ofFloat("alpha", 1.0F, 0.3F, 1.0F)
        val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
            testView,
            valuesHolder1,
            valuesHolder2,
            valuesHolder3
        )
        objectAnimator.setDuration(3000).start()
    }
}

运行程序,效果如图4所示:

图4

六、在 xml 中使用属性动画

和 View 动画一样,属性动画也可以直接写在 xml 中。在 res 文件中新建 animator 文件夹,在里面新建一个 scale.xml,其内容如下所示:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:duration="3000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType" />

在程序中引用 xml 定义的属性动画也很简单,代码如下所示:

xml 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<View>(R.id.test_view)
        val animator = AnimatorInflater.loadAnimator(this, R.animator.scale)
        animator.setTarget(testView)
        animator.start()
    }
}

运行程序,效果如图5所示:

图5

相关推荐
网络研究院19 分钟前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下26 分钟前
android navigation 用法详细使用
android
小比卡丘3 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭4 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss5 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.6 小时前
数据库语句优化
android·数据库·adb
GEEKVIP8 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200510 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68910 小时前
Android广播
android·java·开发语言
与衫11 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql