属性动画(二)

2017-11-18  本文已影响26人  lijiankun24

在上篇文章 属性动画(一) 中已经对属性动画有了基本的介绍,本篇文章将对属性动画中稍微高级点的内容进行介绍,主要介绍下图中绿色部分标明的知识点。

catalog.png

1. Evaluators

Evaluator 是干什么用的呢?简单来说,它是用于告诉动画系统,某种类型的属性值怎么从初始值变化到结束值的。

1.1 TypeEvaluator 解析

系统中自带一些默认的的 Evaluator,比如:IntEvaluatorFloatEvaluatorArgbEvaluator 等等,我们简单分析一下其中的 IntEvaluator 的源码。

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

IntEvaluator 实现了 TypeEvaluator 接口,重写其中的 evaluate(float fraction, Integer startValue, Integer endValue) 方法。该方法中有三个参数值:

evaluate(float fraction, Integer startValue, Integer endValue) 方法内部的实现也很简单:用最终值减去初始值再乘以完成度的,加上初始值,就是动画当前的属性值。

1.2 自定义 Evaluator

仿照 IntEvaluator,我们可以自定义 Evaluator

假设我们现在有一个自定义 View,其颜色是是从纯绿色渐变为纯红色,那该怎么实现的?答案是我们可以通过自定义 Evaluator 来实现。
假设自定义View 的代码如下:

public class CustomView extends View {

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    int color = 0xff00ff00;

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
        invalidate();
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = getWidth();
        int height = getHeight();

        paint.setColor(color);
        canvas.drawCircle(width / 2, height / 2, width / 6, paint);
    }
}

上述自定义 View 的代码其实很简单,根据设置的大小,画一个默认是绿色的圆。

需要注意的是,在该自定义 View 中,如果要改变 color 颜色的属性,该属性需要有一个对应的 setter 的方法,并在其中调用 invalidate() 方法,保证 color 属性变化的时候,都会重新调用 onDraw(Canvas canvas) 重新绘制该 View

那接下来该实现颜色变化的自定义 Evaluator 了,具体代码如下所示:

public class HsvEvaluator implements TypeEvaluator<Integer> {

     private float[] startColor;

     private float[] endColor;

     private float[] midColor;

     public HsvEvaluator() {
         startColor = new float[3];
         endColor = new float[3];
         midColor = new float[3];
     }

     // 重写 evaluate() 方法,让颜色按照 HSV 来变化
     @Override
     public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
         Color.colorToHSV(startValue, startColor);
         Color.colorToHSV(endValue, endColor);

         midColor[0] = startColor[0] + fraction * (endColor[0] - startColor[0]);
         midColor[1] = startColor[1] + fraction * (endColor[1] - startColor[1]);
         midColor[2] = startColor[2] + fraction * (endColor[2] - startColor[2]);

         Integer integer = Color.HSVToColor(midColor);
         return integer;
     }
 }

在其中,主要是重写 evaluate(float fraction, Integer startValue, Integer endValue) 方法,通过 Color.colorToHSV(int color, float hsv[]) 方法,将 Integer 类型的颜色值转换为保存在长度为 3 的 float 数组中,其中每一位对应的是该颜色的 HSV 值。

然后,通过完成度的值 fraction,计算出当前时刻的颜色属性的 HSV 值,并保存在对应的数组中,最后通过 Color.HSVToColor(float hsv[]) 方法转换成 Integer 类型的颜色值,并返回。

那实现该动画的代码如下所示:

    CustomView view = (CustomView) findViewById(R.id.objectAnimatorView);
    ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
    animator.setEvaluator(new HsvEvaluator()); // 使用自定义的 HsvEvaluator
    animator.setInterpolator(new LinearInterpolator());
    animator.setDuration(2000);
    animator.start();

2. Interpolators

Interpolator 直译过来叫做 插值器,定义属性动画的变化率,Interpolator 允许动画不是线性变化的,而是非线性变化的,比如加速变化、减速变化等等。

系统已经默认提供了许多实现了 Interpolator 的类:

2.1 TimeInterpolator

Interpolator 都实现了接口 TimeInterpolator,它的源码还是非常简单的,如下所示:

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

其中只有一个方法,getInterpolation(float input),按照其注释,不难理解。

我们简单分析下 LinearInterpolator 匀速插值器的源码,如下所示:

/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

可以看到,其中的 getInterpolation(float input) 方法的实现非常简单,直接将输入的值 input 作为结果返回,则实现了匀速变化的效果。

再简单分析下 AccelerateDecelerateInterpolator 的源码,如下所示:

public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
    public AccelerateDecelerateInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
    }
}

通过数学做图工具,得到如下图所示的示意图:

cos.png

可以看到:

所以它是一个先加速再减速的过程

2.2 自定义 TimeInterpolator

通过对 LinearInterpolatorAccelerateDecelerateInterpolator 源码的分析,我们也可以实现一个自定义的 Interpolator

我们可以实现一个Interpolator。如下所示:

public class DecelerateAccelerateInterpolator implements BaseInterpolator {

    @Override
    public float getInterpolation(float input) {
        return (float) Math.sin(input * Math.PI);
    }
}

使用数学做图工具,可以得到如下所示的变化示意图:

sin.png

可以看到:

3.Animations in XML

属性动画允许在 xml 文件中定义动画,而不是通过 Java 代码的形式。使用 xml 定义动画,不仅使得重复利用相同的动画更加方便,而且也更容易修改动画。

为区分在 xml 中定义的视图动画,属性动画的 xml 文件放在 res/animator/ 目录下。

属性动画在 xml 中支持以下几个标签:

可以在 这儿Animation Resources 找到属性动画中的所有属性。

以下代码是一个简单的 xml 属性动画的例子:

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

xml 中定义好属性动画之后,在 Java 代码中使用该动画的话,需要先将该动画集合加载进来,之后调用 setTarget() 为该动画设置目标对象即可,如下所示:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();

xml 中也可以通过使用 <animator> 标签定义 ValueAnimator 动画,如下所示:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

Java 代码中使用上面定义的 ValueAnimator 动画,也需要将该 xml 动画加载进来,如下所示:

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});
xmlAnimator.start();

关于在 xml 中定义属性动画更多的信息,请查阅 Animation Resources

4.硬件加速

从 Android 3.0(API level 11),Android 支持硬件加速绘制 2D 渲染,意思就是说在 View 上进行的一些 canvas 操作都使用 GPU 来实现。因为使用硬件加速需要更多的资源,所以会造成内存增加的后果。

API level 大于 14 的时候,硬件加速默认是打开的。如果你的应用只会绘制标准的 ViewDrawable 对象,全局都打开硬件加速并不会造成什么不好的影响。但是硬件加速并不是支持所有的的 2D 绘制操作的,打开硬件加速可能会造成你的自定义控件和一些绘制操作出现异常。为避免造成异常,Android 可以在几个不同的层级上对硬件加速进行开关的控制。

如果开启硬件加速之后,自定义控件和自定义绘制出现了问题,可以在真机上打开硬件加速进行测试,找出问题所在。

4.1 控制硬件加速的开关

总共可以在四个层级上对硬件加速进行控制,如下:

4.1.1 Applicatin 层级

AndroidManifest.xml 文件中,可以通过在 application 中使用以下元素对硬件加速进行控制

<application android:hardwareAccelerated="true" ...>

4.1.2 Activity 层级

如果在 Application 层级对硬件加速控制的不够精细,可以对某个单独的 Activity 中的硬件加速进行开关的控制,如下所示:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

上面代码的意思是,在应用的全局都打开硬件加速,但是在这个 Activity 中关闭了硬件加速。

4.1.3 Window 层级

可以对单独的某个 Window 进行硬件加速的控制,如下代码所示:

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Window 来说,只能打开硬件加速,而不能关闭硬件加速。

4.1.4 View 层级

使用以下代码,可以在运行的时候,把该 View 的硬件加速关闭

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

View 来说,只能关闭硬件加速,而不能打开硬件加速。

4.2 检测硬件加速是否打开

如果在应用中自定义控件和自定义绘制有很多的话,检查硬件加速是否处于打开状态还是非常有必要的,有以下两种方式可以判断:

View.isHardwareAccelerated();
Canvas.isHardwareAccelerated();

4.3 Android 绘制模式

如果硬件加速开启,Android FrameWork 会使用新的绘制模式将应用显示在屏幕上,这种新的绘制模式中使用到了一个关键的概念 ---- display lists

为了完全理解硬件加速相关的知识,首先理解 Android 不使用硬件加速是的软件绘制模式是非常有帮助的。

4.3.1 软件绘制模式

在软件绘制模式中,将 views 绘制到屏幕上需要以下两个步骤:

当应用需要更加 UI 中的某一部分时,有改变的 View (脏区域)需要调用 invalidate()invalidation 信号就会遍历 View 树来计算哪些 views 需要重新绘制,然后 Android 系统会将与这些 views 相关的 views 都进行重新绘制。这种绘制模式有两个缺点:

4.3.2 硬件加速绘制模式

在硬件加速绘制模式中,还是调用 incalidate() 方法和 draw() 方法进行重新绘制的,但是实际上处理绘制的操作还是有所不同的。

在硬件加速绘制模式中,Android 系统不会立即执行绘制指令,而是会先将其记录在 display lists 中,在 display lists 中存储着 view 树的绘制代码,Android 系统只需要将调用了 invalidate() 方法的 views 的绘制代码,记录和更新到 display lists 中即可。那些没有调用 incalidate() 方法的 views,只需要将原来存储于 display lists 中的绘制代码简单地绘制出来即可。

硬件加速绘制模式包括以下三个步骤:

使用这种方式,就不能再使用与脏区域相关的特性而执行本 viewdraw() 方法了。为了使 Android 系统记录和更新一个 viewdisplay lists 的绘制代码,必须调用该 viewinvalidate() 方法了,如果忘记调用 incalidate() 方法,则该 view 不会发生什么变化。

使用 display lists 对属性动画的性能也有很大的好处,一些属性(比如:透明度、旋转角度)并不需要调用 invalidate() 方法即可完成变化,它完全可以自动完成计算。这种特性也会应用在一些普通 View 的操作上。比如,现在有一个 LinearLayout 包含一个 ListView 和一个 Button,此时该 LinearLayoutdisplay lists 是这样的:

假如现在改变 ListView 的透明度,调用 ListViewsetAlpha(0.5f) 方法,那么此时,该 LinearLayoutdisplay lists 如下所示:

复杂的 ListView 的绘制操作计算并没有被执行,相反,系统只需要更新该 LinearLayoutdisplay lists 即可。如果使用软件绘制模式,就会重新计算 ListViewButton 的绘制操作。

4.4 不支持硬件加速的绘制操作

大部分的 Canvas 的方法都支持硬件加速,但是有一些方法是从某些版本之后才开始支持硬件加速的,如下表所示:

unsupported_api1.png
unsupported_api2.png

4.5 View Layers(视图层级)

在 Android 所有的版本中,View 都有能力实现离屏缓冲,实现离屏缓冲的方式:

离屏缓冲有一些很好的用途:当进行于复杂视图的动画,或者一些组合效果时,使用离屏缓冲会有更好的性能。比如实现一个渐变的效果,通过使用 Canvas.saveLayer() 先临时将 view 缓冲到 layers (层级)上,然后再通过一个不透明因子将其绘制到屏幕上。

从 Android 3.0 (API level 11)开始,通过使用 View.setLayerType() 方法,对什么时候以及怎样使用视图层级有了更高的控制权限。在该方法中有两个参数:

对于视图类型参数的选择,从以下几个因素考虑:

4.6 视图层级和动画

如果应用开启了硬件加速,使用硬件层级绘制一些复杂的动画会更快且更平滑。如果有一些很复杂的动画,每秒中进行 60 帧的绘制会是一个问题,有时候会掉帧,这时候使用硬件加速将 View会知道 hardware texture 之上,就会有一定的改善。

View 不会进行重新绘制,除非调用了 invalidate() 方法。如果在应用中设置了一个动画,但是动画并不平滑,可以考虑使用硬件加速优化动画效果。

如果开启了硬件加速,在 View 中有一些属性的改变并不会对该 View 进行重新绘制操作,而是直接改变图层上的绘制代码:

例如下面这样的动画代码效率会更高,而且更平滑:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

因为硬件加速会占用很高的内存,所以一般是在使用的时候开启硬件加速,不使用的时候关闭硬件加速,如下所示:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

4.7 一些建议

虽然使用硬件加速可以提高 View 的显示效果,提高性能,但是还是应该更加合理的使用硬件加速,不能滥用。对此给出以下几点建议:


参考资料:

动画官方文档

硬件加速官方文档

HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇) -- HenCoder

HenCoder Android 自定义 View 1-8 硬件加速 -- HenCoder

HenCoder Android 自定义 View 1-8 硬件加速 -- HenCoder

Android硬件加速原理与实现简介) -- 美团点评技术团队

关于硬件加速的那么点儿东西 -- 黑白咖

上一篇下一篇

猜你喜欢

热点阅读