Android 动画
- 逐帧动画(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”
- 补间动画(Tween Animiation/ View Animation)
指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。可以在 一个视图容器内执行一系列 简单变换。建议使用 XML 文件,更具可读性、可重用性。
alpha,translate,scale,rotate。
常用属性:
通用:duration,fillAfter,interpolator,repeatCount,repeatMode
不通用:from**,to**
AnimationSet:一个持有其它动画元素 alpha、scale、translate、rotate 或者其它 set 元素的容器。
对 set 标签使用 Animation 的属性时会对该标签下的所有子控件都产生影响。
特点:他的动画仅仅是动的 View 的绘制地方,View 真正的位置并没有改变。应用场景:进场动画,过场动画、 - 属性动画(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; } }
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,不然会重复执行。
- 在开始动画时调用
-
还有一种补间动画的特殊用法: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