属性动画备忘

1、属性动画流程

2、相关API:


Property Animation故名思议就是通过动画的方式改变对象的属性了,我们首先需要了解几个属性:

  1. Duration:动画的持续时间,默认300ms。
  2. Time interpolation:时间差值,乍一看不知道是什么,但是我说LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定义动画的变化率。
  3. Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
  4. Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。
  5. Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。
  6. ObjectAnimator 动画的执行类,后面详细介绍
  7. ValueAnimator 动画的执行类,后面详细介绍
  8. AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
  9. AnimatorInflater 用户加载属性动画的xml文件
  10. *TypeEvaluator * 类型估值,主要用于设置动画操作属性的值。
  11. TimeInterpolator 时间插值。

总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。

3、ObjectAnimator实例

ObjectAnimator
        .ofFloat(view, "rotationX", 0.0F, 360.0F)
        .setDuration(1000)
        .start();

这个例子很简单,针对view的属性rotationX进行持续时间为1000ms的0到360的角度变换。

常见的动画属性:

  1. translationX和translationY:这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。
  2. rotation、rotationX和rotationY:这三个属性控制View对象围绕支点进行2D和3D旋转。
  3. scaleX和scaleY:这两个属性控制着View对象围绕它的支点进行2D缩放。
  4. pivotX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。
  5. x和y:这是两个简单实用的属性,它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX和translationY值的累计和。
  6. alpha:它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。

4、ObjectAnimator动画监听

当然这个可以使用Animationset来实现,这里我们使用一种取巧的方法来实现:

ObjectAnimator anim = ObjectAnimator.ofFloat(view, "xxx", 1.0F, 0.0F)
        .setDuration(500);
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        floatcVal = (Float) animation.getAnimatedValue();
        view.setAlpha(cVal);
        view.setScaleX(cVal);
        view.setScaleY(cVal);
    }
});

我们可以监听一个并不存在的属性,而在监听动画更新的方法中,去修改view的属性,监听一个不存在的属性的原因就是,我们只需要动画的变化值,通过这个值,我们自己来实现要修改的效果,实际上,更直接的方法,就是使用ValueAnimator来实现,其实ObjectAnimator就是ValueAnimator的子类,这个在下面会具体讲到。

5、为不具有get/set方法的属性提供包装类—WrapperView

Google在应用层为我们提供了2种解决方法,一种是通过自己写一个包装类,来为该属性提供get/set方法,还有一种是通过ValueAnimator来实现,ValueAnimator的方法我们在下面会具体讲解,这里讲解下如何使用自定义的包装类来给属性提供get/set方法。
包装类:

private static class WrapperView {
    private View mTarget;

    public WrapperView(View target) {
        mTarget = target;
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

使用方法:

ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();

这样就间接给他加上了get/set方法,从而可以修改其属性实现动画效果。

6、多动画效果的另一种实现方法——PropertyValuesHolder

public void propertyValuesHolder(View view) {
    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,
            0f, 1f);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,
            0, 1f);
    PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,
            0, 1f);
    ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ)
            .setDuration(1000).start();
}

7、ValueAnimator 实例

说简单点,ValueAnimator就是一个数值产生器,他本身不作用于任何一个对象,但是可以对产生的值进行动画处理。

ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
animator.setTarget(view);
animator.setDuration(1000).start();
animator.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Float value = (Float) animation.getAnimatedValue();
        imageView.setTranslationY(value);
    }
});

8、AnimatorSet实例

AnimatorSet中有一系列的顺序控制方法,用来实现多个动画的协同工作方式:

playTogether:
playSequentially:
animSet.play().with():
defore():
after():

ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "scaleX",
        1f, 2f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "scaleY",
        1f, 2f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView,
        "translationY", 0f, 500f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(animator1, animator2, animator3);
set.start();

9、使用xml来创建动画

属性动画与以前的动画一样,也支持通过xml文件来创建动画,下面是一个简单的例子:
定义动画的XML文件

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType" >
</objectAnimator>

加载动画的XML文件

public void scaleX(View view)
{
    Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
    anim.setTarget(mMv);
    anim.start();
}

10、View的animate方法

3.0后android对View也提供了直接作用的动画API:

view.animate().alpha(0).y(100).setDuration(1000)
        .withStartAction(new Runnable() {
            @Override
            public void run() {
            }
        }).withEndAction(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
            }
        });
    }
}).start();

11、Interpolators(插值器)

插值器和估值器,是实现非线性动画的基础,了解这些东西,才能作出不一样的动画效果。所谓的插值器,就是通过一些数学物理公式,计算出一些数值,提供给动画来使用。

就好比我们定义了起始值是0,结束值是100,但是这0到100具体是怎么变化的呢,这就是插值器产生的结果,线性的,就是匀速增长,加速的,就是按加速度增长。这些增加的算法公式,已经不需要我们来自己设计了,Android内置了9种插值器,基本可以满足需求,当然你也可以自定新的插值器。点击上面的链接,也可以查看自定义插值器的方法。下面简单介绍下:

AccelerateInterpolator 加速
DecelerateInterpolator 减速
AccelerateDecelerateInterpolator 开始,和结尾都很慢,但是,中间加速
AnticipateInterpolator 开始向后一点,然后,往前抛
OvershootInterpolator 往前抛超过一点,然后返回来
AnticipateOvershootInterpolator 开始向后一点,往前抛过点,然后返回来
BounceInterpolator 结束的时候弹一下
LinearInterpolator 默认 匀速
CycleInterpolator 周期插值器

自定义Interpolator:
Interpolator的关键在于公式。
例如:我想创建的Interpolator名字叫HesitateInterPolator:起始时速度全速,快到一半时减速到0,然后加速直到终点, 用图形和公式来表示即:
自定义Interpolator图示

自定义Interpolator公式

我们只需要实现Interpolator接口,然后实现getInterpolation()方法即可:

public class HesitateInterpolator implements Interpolator {
    public HesitateInterpolator() {}
    public float getInterpolation(float t) {
      float x=2.0f*t-1.0f;
      return 0.5f*(x*x*x + 1.0f);
    }
}

12、TypeEvalutors (估值器)

根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值。

android提供了以下几个evalutor:

  1. IntEvaluator:属性的值类型为int;
  2. FloatEvaluator:属性的值类型为float;
  3. ArgbEvaluator:属性的值类型为十六进制颜色值;
  4. TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。

自定义TypeEvalutor
自定义TypeEvalutor也很简单,只需要继承TypeEvaluator,然后实现evaluate()方法即可。

public class ColorEvaluator implements TypeEvaluator {  

    private int mCurrentRed = -1;  

    private int mCurrentGreen = -1;  

    private int mCurrentBlue = -1;  

    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        String startColor = (String) startValue;  
        String endColor = (String) endValue;  
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);  
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);  
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);  
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);  
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);  
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);  
        // 初始化颜色的值  
        if (mCurrentRed == -1) {  
            mCurrentRed = startRed;  
        }  
        if (mCurrentGreen == -1) {  
            mCurrentGreen = startGreen;  
        }  
        if (mCurrentBlue == -1) {  
            mCurrentBlue = startBlue;  
        }  
        // 计算初始颜色和结束颜色之间的差值  
        int redDiff = Math.abs(startRed - endRed);  
        int greenDiff = Math.abs(startGreen - endGreen);  
        int blueDiff = Math.abs(startBlue - endBlue);  
        int colorDiff = redDiff + greenDiff + blueDiff;  
        if (mCurrentRed != endRed) {  
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,  
                    fraction);  
        } else if (mCurrentGreen != endGreen) {  
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,  
                    redDiff, fraction);  
        } else if (mCurrentBlue != endBlue) {  
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,  
                    redDiff + greenDiff, fraction);  
        }  
        // 将计算出的当前颜色的值组装返回  
        String currentColor = "#" + getHexString(mCurrentRed)  
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);  
        return currentColor;  
    }  

    /** 
     * 根据fraction值来计算当前的颜色。 
     */  
    private int getCurrentColor(int startColor, int endColor, int colorDiff,  
            int offset, float fraction) {  
        int currentColor;  
        if (startColor > endColor) {  
            currentColor = (int) (startColor - (fraction * colorDiff - offset));  
            if (currentColor < endColor) {  
                currentColor = endColor;  
            }  
        } else {  
            currentColor = (int) (startColor + (fraction * colorDiff - offset));  
            if (currentColor > endColor) {  
                currentColor = endColor;  
            }  
        }  
        return currentColor;  
    }  

    /** 
     * 将10进制颜色值转换成16进制。 
     */  
    private String getHexString(int value) {  
        String hexString = Integer.toHexString(value);  
        if (hexString.length() == 1) {  
            hexString = "0" + hexString;  
        }  
        return hexString;  
    }  

}  

首先,Interplator会根据当前的执行时间得到一个fraction时间因子,
然后,Interplator将fraction时间因子传给TypeEvaluator,最后,TypeEvaluator会计算出该时间因子下的类型参数。

13、KeyFrame 关键帧

keyFrame是一个 时间/值 对,通过它可以定义一个在特定时间的特定状态,即关键帧,而且在两个keyFrame之间可以定义不同的Interpolator,就好像多个动画的拼接,第一个动画的结束点是第二个动画的开始点。

KeyFrame是抽象类,要通过ofInt(),ofFloat(),ofObject()获得适当的KeyFrame,然后通过PropertyValuesHolder.ofKeyframe获得PropertyValuesHolder对象,如:

Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);

上述代码的意思为:设置btn对象的width属性值使其:
开始时 Width=400
动画开始1/4时 Width=200
动画开始1/2时 Width=400
动画开始3/4时 Width=100
动画结束时 Width=500

第一个参数为时间百分比,第二个参数是在第一个参数的时间时的属性值。

定义了一些Keyframe后,通过PropertyValuesHolder类的方法ofKeyframe一个PropertyValuesHolder对象,然后通过ObjectAnimator.ofPropertyValuesHolder获得一个Animator对象。

用下面的代码可以实现同样的效果(上述代码时间值是线性,变化均匀):

ObjectAnimator oa=ObjectAnimator.ofInt(btn2, "width", 400,200,400,100,500);
oa.setDuration(2000);
oa.start();

14、View Anim 与 property Anim 的区别

View anim 系统:

  1. 【劣势】view animation system 提供的能力只能够为 View 添加动画。因此如果你想为非 View 对象添加动画,就必须自己去实现, view animation system 在 View 动画的展现方面也是有约束的,只暴露了 View 的很少方面。比如 View 支持缩放和旋转,但不支持背景颜色的动画。
  2. 【劣势】view animation system 的另一劣势是,其改变的是 View 的绘制效果,真正的 View 的属性保持不变,比如无论你在对话中如何缩放 Button 的大小,Button 的有效点击区域还是没有应用到动画时的区域,其位置与大小都不变。
  3. 【优势】但是 View animation system 只需花费很少时间创建而且只需很少的代码。如果 View 动画完成了你所有的动作,或者你存在的代码已经达到了你想要的效果,就没必要使用 property 动画系统了。

property anim 系统

  1. 完全弥补了 View anim System 的缺陷,你可以为一个对象的任何属性添加动画,(View 或者非 View),同时对象自己也会被修改。 并且当属性变化的时候,property Anim 系统会自动的刷新屏幕。
  2. 属性动画系统在处理动画方面也更加强劲。更高级的,你可以指定动画的属性,比如颜色,位置,大小,定义动画的插值器并且同步多个动画。
  3. 并且在 Property Animation 中,改变的是对象的实际属性,如 Button 的缩放,Button 的位置与大小属性值都改变了。而且 Property Animation 不止可以应用于 View,还可以应用于任何对象。
  4. 平时使用的简单动画特效,使用 View 动画就可以满足,但是如果你想做的更加复杂,比如背景色的动画,或者不仅是 View,还希望对其它对象添加动画等,那么你就得考虑使用 Property 动画了。
  5. 更多动画开源库及使用,可以参考:Android 动画系列,其中介绍了一些基本使用,也提到了一些 GitHub 上的动画开源库,可以作为 Android 动画学习的资料

参考资料:

  1. Android动画机制全解析
  2. Android属性动画(Property Animation)完全解析
2016-10-29 11:0920