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)
- 指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。可以在一个视图容器内执行一系列简单变换。
alpha,translate,scale,rotate。 - 建议使用 XML 文件,更具可读性、可重用性。
- 常用属性:
通用: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;
}
}
- 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,不然会重复执行。
- 还有一种补间动画的特殊用法: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