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”

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

  5. 常用属性:
    通用:duration,fillAfter,interpolator,repeatCount,repeatMode
    不通用:from**,to**
    AnimationSet:一个持有其它动画元素 alpha、scale、translate、rotate 或者其它 set 元素的容器。
    对 set 标签使用 Animation 的属性时会对该标签下的所有子控件都产生影响。
    特点:他的动画仅仅是动的 View 的绘制地方,View 真正的位置并没有改变。应用场景:进场动画,过场动画、   
  6. 属性动画(Property Animation) 功能最全面,包含补件动画的功能。

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

  1. 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的区别。

android 帧动画java android 帧动画优化_android_02

所以可以简化上述代码,直接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; 
    }
}
  1. 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,不然会重复执行。

  1. 还有一种补间动画的特殊用法: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);


效果如图:

android 帧动画java android 帧动画优化_ide_03


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)为控制点绘制贝塞尔曲线:

android 帧动画java android 帧动画优化_android 帧动画java_04

 

 

用作图像处理的Matrix

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

android 帧动画java android 帧动画优化_android 帧动画java_05

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

Translate           平移变换

Rotate                旋转变换

Scale                  缩放变换

Skew                  错切变换

 

 

几种实际操作例子:

1. 进度条

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