【Android开发】 View的属性动画

View的属性动画

在许多的场景中,我们可能不只需要简单的移动,旋转,透明,变大缩小等等,我们需要加入持续的时间,定格的时间,多种组合特效等等,于是我们需要使用 Android 的动画能力。在早期的 Android 中,只有帧动画和 View 动画。View 动画提供了 AlphaAnimationTranslateAnimationRotateAnimationScaleAnimation 这4种动画方式。但他们不具有交互性,即当某个元素发生 View 动画之后,其响应事件的位置依然在动画进行前的地方,所以 View 动画只能做普通的动画效果,要避免交互操作。于是我们使用 ObjectAnimator 进行更精细化的控制,控制一个对象和一个属性值,使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。


ValueAnimator

ObjectAnimator 是继承了 ValueAnimator, 而 ValueAnimator 是不提供任何动画效果,它只是一个数值发生器,用来产生有一定规律的数字从而让调用者控制动画的实现过程。

ValueAnimator 可以通过代码或者资源文件创建。以下是它的资源文件实例:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

API23 开始,还可以使用 PropertyValuesHolderKeyframe 资源标签的组合来创建多步动画。

<animator xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="1000"
          android:repeatCount="1"
          android:repeatMode="reverse">
    <propertyValuesHolder>
        <keyframe android:fraction="0" android:value="1"/>
        <keyframe android:fraction=".2" android:value=".4"/>
        <keyframe android:fraction="1" android:value="0"/>
    </propertyValuesHolder>
</animator>

注意:可以为每个关键帧指定明确的分数值(从 0 到 1),以确定动画在整个持续时间内何时到达该值。

也可以在代码中创建一个 ValueAnimatorValueAnimator 提供了一个 AnimatorUpdateListener() 监听数值的变化。

	val valueAnimator = ValueAnimator.ofFloat(0F, 100F)
	valueAnimator.setTarget(it)
	valueAnimator.duration = 1000
	valueAnimator.start()
	valueAnimator.addUpdateListener { animation ->
	    val mFloat = animation?.animatedValue
	    println(mFloat)
	}

ObjectAnimator

创建一个 ObjectAnimator 只需通过其静态工厂类直接返还一个 ObjectAnimator 对象。参数包括一个对象和对象的属性名字,但是这个属性必须有 getset 方法。下面是旋转动画的示例:

	val objectAnimator = ObjectAnimator.ofFloat(it, "rotationX", 180.toFloat())
	objectAnimator.duration = 1000
	objectAnimator.start()

查看 ObjectAnimatorofFloat(),第一个参数是要操作的 Object, 第二个参数是要操作的属性,最后一个参数是一个可变的 float 类型的可变数组,这里设置了旋转到180度。

注意:这边是每次都旋转到180度,如果是要每次点击旋转180度,需要将第三个参数修改成(it.rotationX + 180).toFloat()

    /**
     * Constructs and returns an ObjectAnimator that animates between float values. A single
     * value implies that that value is the one being animated to, in which case the start value
     * will be derived from the property being animated and the target object when {@link #start()}
     * is called for the first time. Two values imply starting and ending values. More than two
     * values imply a starting value, values to animate through along the way, and an ending value
     * (these values will be distributed evenly across the duration of the animation).
     *
     * @param target The object whose property is to be animated. This object should
     * have a public method on it called <code>setName()</code>, where <code>name</code> is
     * the value of the <code>propertyName</code> parameter.
     * @param propertyName The name of the property being animated.
     * @param values A set of values that the animation will animate between over time.
     * @return An ObjectAnimator object that is set up to animate between the given values.
     */
    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

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

一个完整的动画,会具有 StartRepeatEndCancel 这4个过程,可以分别对四个过程进行监听。

    objectAnimator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator?) {
            Log.i(TAG, "start")
        }

        override fun onAnimationEnd(animation: Animator?) {
            Log.i(TAG, "end")
        }

        override fun onAnimationCancel(animation: Animator?) {
            Log.i(TAG, "cancel")
        }

        override fun onAnimationRepeat(animation: Animator?) {
            Log.i(TAG, "repeat")
        }
    })

但是绝大多数的时候,我们只关心 onAnimationEnd 事件, Android 也提供了 AnimatorListenerAdapter 来让我们选择必要的事件进行监听。

    objectAnimator.addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator?) {
            super.onAnimationEnd(animation)
        }
    })

AnimatorSet

上面讲到,一个 ObjectAnimator 对应了一个动画,有些时候,我们需要多个动画组合,例如先左移,再旋转,再放大等等。这就需要一个将这些动画组合起来的容器。Android 也为我们提供了一个 AnimatorSet 类来完成组合动画的功能。AninmatorSet 类提供了一个 play() 方法,我们向这个方法中传入一个 Animator 对象( ValueAnimatorObjectAnimator ),将会返回一个 AnimatorSet.Builder 的实例。通过 play() 方法初始化的 Builder 可以往后面插入连续的 Animator 并且决定它们的排列顺序。

public class Builder {

        /**
         * This tracks the current node being processed. It is supplied to the play() method
         * of AnimatorSet and passed into the constructor of Builder.
         */
        private Node mCurrentNode;

        /**
         * package-private constructor. Builders are only constructed by AnimatorSet, when the
         * play() method is called.
         *
         * @param anim The animation that is the dependency for the other animations passed into
         * the other methods of this Builder object.
         */
        Builder(Animator anim) {
            mDependencyDirty = true;
            mCurrentNode = getNodeForAnimation(anim);
        }

        /**
         * Sets up the given animation to play at the same time as the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
         *
         * @param anim The animation that will play when the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method starts.
         */
        public Builder with(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addSibling(node);
            return this;
        }

        /**
         * Sets up the given animation to play when the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
         * ends.
         *
         * @param anim The animation that will play when the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method ends.
         */
        public Builder before(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addChild(node);
            return this;
        }

        /**
         * Sets up the given animation to play when the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
         * to start when the animation supplied in this method call ends.
         *
         * @param anim The animation whose end will cause the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method to play.
         */
        public Builder after(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addParent(node);
            return this;
        }

        /**
         * Sets up the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
         * to play when the given amount of time elapses.
         *
         * @param delay The number of milliseconds that should elapse before the
         * animation starts.
         */
        public Builder after(long delay) {
            // setup dummy ValueAnimator just to run the clock
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(delay);
            after(anim);
            return this;
        }

    }

通过查看 AnimatorSetBuilder 内部类的源码,可以看到它主要提供了4个方法来控制动画顺序。

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

AnimatorSet 用法如下:

    val objectAnimator1 = ObjectAnimator.ofFloat(it, "rotationX", it.rotationX + 180)
    val objectAnimator2 = ObjectAnimator.ofFloat(it, "translationX", it.translationX + 100)
    val objectAnimator3 = ObjectAnimator.ofFloat(it, "scaleX", it.scaleX + 1)
    val set = AnimatorSet()
    set.duration = 1000
    set.play(objectAnimator1).with(objectAnimator2).after(objectAnimator3)
    set.start()

PropertyValuesHolder

除了 AnimatorSet 类,还可以使用 PropertyValuesHolder 类来实现组合动画。不过使用 PropertyValuesHolder 只能是多个动画一起执行。我们使用 ObjectAnimator.ofPropertyValuesHolder(Object target, PropertyValuesHolder… values) 方法,其中第一个参数是动画的目标对象,之后的参数是 PropertyValuesHolder 类的实例。

    val propertyValuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", it.scaleX + 1)
    val propertyValuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", it.rotationX + 180)
    val propertyValuesHolder3 = PropertyValuesHolder.ofFloat("translationX", it.translationX + 100)
    val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(it, propertyValuesHolder1, propertyValuesHolder2, propertyValuesHolder3)
    objectAnimator.duration = 2000
    objectAnimator.start()

《Android进阶之光》刘望舒 电子工业出版社

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hovf-1120

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值