Android动画有哪些 及 优化方式总结

Android 动画

  1. 逐帧动画(Drawable Animation):让图片动起来
    一系列静态图片-》控制依次显示及时长,视觉暂留,通常XML:
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                      android:oneshot="true|false">
          <item android:drawable="" android:duration=""/>
     </animation-list>
    <ImageView
            android:layout_marginTop="50dp"
            android:layout_centerHorizontal="true"
            android:id="@+id/frame_image"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@drawable/frame_animation"
            />

    Activity 中控制播放与停止:

    // 获取 AnimationDrawable 对象
    animationDrawable = (AnimationDrawable) frame_image.getBackground();
    animationDrawable.start();

    属性:duration

    应用场景:做一个“GIF”

  2. 补间动画(Tween Animiation/ View Animation)
    指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。
    可以在一个视图容器内执行一系列简单变换。
    alpha,translate,scale,rotate。
    建议使用 XML 文件,更具可读性、可重用性。

    常用属性:

    通用:duration,fillAfter,interpolator,repeatCount,repeatMode

    不通用:from**,to**

    AnimationSet:一个持有其它动画元素 alpha、scale、translate、rotate 或者其它 set 元素的容器。

    对 set 标签使用 Animation 的属性时会对该标签下的所有子控件都产生影响。

    特点:他的动画仅仅是动的 View 的绘制地方,View 真正的位置并没有改变。
    应用场景:进场动画,过场动画、
     
     
     
  3. 属性动画(Property Animation)
    功能最全面,包含补件动画的功能。

    ValueAnimator:时间驱动,管理着动画时间的开始、结束属性值,相应时间属性值计算方法等。

    ObjectAnimator:继承自 ValueAnimator,允许你指定要进行动画的对象以及该对象的一个属性。该类会根据计算得到的新值自动更新属性。 大多数的情况使用 ObjectAnimator 就足够了。
    ofInt、ofFloat、ofObject(ofFloat常用于view的动画,原因是精度足够,形成流畅的动画效果)
    ObjectAnimator.ofFloat(view, "rotationY", 0.0f, 360.0f).setDuration(1000).start();

    AnimatorSet:动画集合,提供把多个动画组合成一个组合的机制,并可设置动画的时序关系,如同时播放、顺序播放或延迟播放。

    ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
    ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
    ......
    AnimatorSet animSet = new AnimatorSet();  
    animSet.setDuration(5000);  
    animSet.setInterpolator(new LinearInterpolator());   
    //animSet.playTogether(a1, a2, ...); //两个动画同时执行  
    animSet.play(a1).after(a2); //先后执行
    ......//其他组合方式
    animSet.start();  
    特点:修改控件的属性值实现;无法设置结束后view的位置停留在哪里;
    ASet.addListener(new AnimatorListenerAdapter() {
                float x;
                float y;
                float rotate;
                float alpha;
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
    //                LogUtil.d("showRabbit, ASet开始");
                    imageView.setVisibility(VISIBLE);
                    textView.setVisibility(VISIBLE);
                    x = imageView.getTranslationX();
                    y = imageView.getTranslationY();
                    rotate = imageView.getRotation();
                    alpha = textView.getAlpha();
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
    //                LogUtil.d("showRabbit, ASet结束");
                    imageView.setVisibility(INVISIBLE);
                    imageView.setLayerType(View.LAYER_TYPE_NONE,null);
                    imageView.setTranslationX(x);
                    imageView.setTranslationY(y);
                    imageView.setRotation(rotate);
                    textView.setAlpha(alpha);
                    ASet.removeAllListeners();
    
                }
            });

    如果想要target在结束动画之后再回到原来的位置,其中一种解决办法如上:

    注意:1. TranslationX与X的区别。

    所以可以简化上述代码,直接setTranslationX为0:

    ASet.addListener(new AnimatorListenerAdapter() {
                float rotate;
                float alpha;
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
    //                LogUtil.d("showRabbit, ASet开始");
                    imageView.setVisibility(VISIBLE);
                    textView.setVisibility(VISIBLE);
                    rotate = imageView.getRotation();
                    alpha = textView.getAlpha();
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
    //                LogUtil.d("showRabbit, ASet结束");
                    imageView.setVisibility(INVISIBLE);
                    imageView.setLayerType(View.LAYER_TYPE_NONE,null);
                    imageView.setTranslationX(0);
                    imageView.setTranslationY(0);
                    imageView.setRotation(rotate);
                    textView.setAlpha(alpha);
                    ASet.removeAllListeners();
    
                }
            });

    2. Listener的初始化一定要写在动画开始之前。

    属性动画的优化方法:

    1. 硬件加速

    • 在开始动画时调用View.setLayerType(View.LAYER_TYPE_HARDWARE, null)
    • 运行动画
    • 动画结束时调用View.setLayerType(View.LAYER_TYPE_NONE, null).

    提高动画绘制速度:增加GPU绘制工作,减少CPU绘制工作;但是增加了CPU把图层缓存到GPU的工作开销。

    现在项目的动画问题最主要出在动画部分临时变量多,GC触发频繁,内存泄漏。

    2. 减少临时变量

    ObjectAnimator的源码,我们可以知道它原理是使用一个成员变量,PropertyValuesHolder,来管理单个属性。

    PropertyValuesHolder:可以用在多属性动画同时工作管理。

    一个view同时发生多种属性效果时,建议这种写法。

    效果具体如下:

    不用PropertyValuesHolder,只用ObjectAnimator:

    ObjectAnimator a1 = PropertyValuesHolder.ofFloat(view, "alpha", 0f, 1f);
    ObjectAnimator a2 = PropertyValuesHolder.ofFloat(view, "translationY", 0, viewWidth);
    ......
    AnimatorSet set = new AnimatorSet();
    set.playTogether(a1,a2,.....);
    set.setDuration(1000)
    set.start();

    用PropertyValuesHolder管理属性:

    PropertyValuesHolder a1 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
    PropertyValuesHolder a2 = PropertyValuesHolder.ofFloat("translationY", 0, viewWidth);
    ......
    ObjectAnimator.ofPropertyValuesHolder(view, a1, a2, ......).setDuration(1000).start();

    Keyframe

    Keyframe是PropertyValuesHolder的成员,用来管理每一个关键帧的出现时间。

    一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。

    效果如下:

    不用Keyframe,只用ObjectAnimator:

    AnimatorSet animSet = new AnimatorSet(); 
    ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(mView, "translationY", 0, 100); 
    ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(mView, "translationY", 100, 0); 
    animSet.playSequentially(transYFirstAnim, transYSecondAnim);

    用Keyframe管理关键帧的时序性:

    Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地” 
    Keyframe k1 = Keyframe.ofFloat(0.5f, 100); 
    Keyframe k2 = Keyframe.ofFloat(1f, 0); 
    PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2); 
    ObjectAnimator objectAnimator =ObjectAnimator.ofPropertyValuesHolder(mView, p);
    objectAnimator.start();

    总的来说就是:ObjectAnimator把属性值的更新委托给PropertyValuesHolder执行,PropertyValuesHolder再把关键帧的时序性计算委托给Keyframe。

    最后,不同的view再用不同的ObjectAnimator管理。

    减少了ObjectAnimator的数量,减少了内存使用,复用可以复用的部分。

     

    3.内存泄漏

    (1)如果用到了无限循环的动画,会这么写:animator.setRepeatCount(ValueAnimator.INFINITE);

    如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露。

    取消不能用clearAnimation(); 要用animator.end();或者animator.cancel();

    (两者区别是cancel方法会立即停止动画,并且停留在当前帧。end方法会立即停止动画,并且将动画迅速置到最后一帧的状态。)

    (2)

    ObjectAnimator持有target:

    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }
    @Override
    public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // New target should cause re-initialization prior to starting
            // 记录尚未初始化,ValueAnimator的初始化标志位
            mInitialized = false; 
        }
    }
    ObjectAnimator持有target是一个弱引用,只要target不被其他不能释放的对象持有,就不会出现target导致内存泄漏的问题;但是反过来target有没有可能持有animator,如果target生命周期持续下去,而导致animator不被释放,越积累越多呢,我的项目中就出现了内存持续上涨这个问题,主动的end和cancel好像也不能释放他们,而且在复用了ObjectAnimator后得到解决,所以怀疑是ObjectAnimator没有释放;还有一个因素值得怀疑:
    public void setValues(PropertyValuesHolder... values) {
            int numValues = values.length;
            mValues = values;
            mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
            for (int i = 0; i < numValues; ++i) {
                PropertyValuesHolder valuesHolder = values[i];
                mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
            }
            // New property/values/target should cause re-initialization prior to starting
            mInitialized = false;
        }

    ObjectAnimator源码中有如上这一段,创建了一个hashmap,名字叫mValuesMap,然而这个map没有找到销毁它的地方,所以如果不停创建ObjectAnimator,但是ObjectAnimator不会被回收,也有可能是这个原因。

    (3)

    AnimatorSet可以多次装载Animator,所以AnimatorSet如果写成静态或全局的,想要复用它,那在复用前要清空set,不然会重复执行。

     

     

  4. 还有一种补间动画的特殊用法:Animation的applyTransformation()方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下60FPS,是一个非常流畅的画面了,也就是16ms。我们可以覆写这个方法,快速的制作自己的动画。

    飘心在上升过程中的缩放,旋转和透明度变化,是用这种方式做的.

    @Override
            protected void applyTransformation(float factor, Transformation transformation) {
                Matrix matrix = transformation.getMatrix();
                mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
                mView.setRotation(mRotation * (factor/2f+0.5f));
                float scale = 1F;
                if (3000.0F * factor < 200.0F) {
                    scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D);
                } else if (3000.0F * factor < 300.0F) {
                    scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D);
                }
                mView.setScaleX(scale);
                mView.setScaleY(scale);
                transformation.setAlpha(1.0F - factor);
            }

     

    

 

 

  • 贝塞尔曲线:


1、moveTo:不会进行绘制,只用于移动移动画笔。

2、quadTo :用于绘制圆滑曲线,即贝塞尔曲线。

mPath.quadTo(x1, y1, x2, y2) (x1,y1) 为控制点,(x2,y2)为结束点。同样地,我们还是得需要moveTo来协助控制。

mPath.moveTo(100, 500);
mPath.quadTo(300, 100, 600, 500);
canvas.drawPath(mPath, mPaint);

效果如图:

3、cubicTo:同样是用来实现贝塞尔曲线的。

mPath.cubicTo(x1, y1, x2, y2, x3, y3) (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。多了一个控制点而已。

mPath.moveTo(100, 500);
mPath.cubicTo(100, 500, 300, 100, 600, 500);

结果和上图一样。

如果我们不加 moveTo 呢?则以(0,0)为起点,(100,500)和(300,100)为控制点绘制贝塞尔曲线:

 

 

  • 用作图像处理的Matrix

Android中的Matrix是一个3 x 3的矩阵,其内容如下:

Matrix的对图像的处理可分为四类基本变换:

Translate           平移变换

Rotate                旋转变换

Scale                  缩放变换

Skew                  错切变换

 

 

几种实际操作例子:

1. 进度条

https://www.jianshu.com/p/5bee6048ed22

 

posted @ 2019-08-06 15:57  粥粥Sophie  阅读(1750)  评论(0编辑  收藏  举报