1. Android动画分类:
- View动画
- 帧动画
- 属性动画
1. View动画
- View动画的作用对象是View
- View动画是对View的影像做动画,并不会真正地改变View的状态(如View的可见性,View的位置等).
- 有时候出现动画完成后View无法隐藏问题,即view.setVisibility(View.GONE)失效,这时调用view.clearAnimation()清除View动画即可解决.
- View动画包含4种
- 平移动画 TranslateAnimation
- 缩放动画 ScaleAnimation
- 旋转动画 RotateAnimation
- 透明度动画 AlphaAnimation
- View动画的几个方法
- setFillBefore
- View动画结束后,是否还原到动画开始前的状态
- setFillEnabled
- 经试验效果同 setFillBefore
- setFillAfter
- View动画结束后,是否保持在动画最后的状态
- setZAdjustment : 没有试验出什么效果
- setHasRoundedCorners : 不知道怎么使用
-
<View android:id="@+id/v1" android:layout_width="200dp" android:layout_height="200dp" android:background="@color/colorPrimary" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:background="@android:color/transparent" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.0" android:layout_margin="2dp" android:background="@color/colorAccent" android:text="setFillAfter true" android:onClick="setFillAfterTrue" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.0" android:layout_margin="2dp" android:background="@color/colorAccent" android:text="setFillBefore true" android:onClick="setFillBeforeTrue" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.0" android:layout_margin="2dp" android:background="@color/colorAccent" android:text="setFillEnabled true" android:onClick="setFillEnabledTrue" /> </LinearLayout>private ScaleAnimation gainScaleAnimation(){ ScaleAnimation scaleAnimation = new ScaleAnimation(0.5F,1.5F,0.5F,1.5F); scaleAnimation.setDuration(4000); return scaleAnimation; } public void setFillAfterTrue(View view) { v1.clearAnimation(); ScaleAnimation scaleAnimation = gainScaleAnimation(); //经试验,单独使用 setFillAfter(true) ,以及使用 setFillAfter(true) + setFillEnabled(true), //效果是一致的,都会保持动画最后的状态 scaleAnimation.setFillAfter(true); scaleAnimation.setFillEnabled(true); v1.startAnimation(scaleAnimation); } public void setFillBeforeTrue(View view) { v1.clearAnimation(); ScaleAnimation scaleAnimation = gainScaleAnimation(); scaleAnimation.setFillBefore(true); v1.startAnimation(scaleAnimation); } public void setFillEnabledTrue(View view) { v1.clearAnimation(); ScaleAnimation scaleAnimation = gainScaleAnimation(); scaleAnimation.setFillEnabled(true); v1.startAnimation(scaleAnimation); }
- setFillBefore
- TimeInterpolator/插值器
- A time interpolator defines the rate of change of an animation.
- 插值器定义了 动画执行时间 和 动画执行完成度 的对应关系.Android提供了多种实现,不详述.
- View动画的特殊使用场景
- LayoutAnimation/布局动画
- LayoutAnimation用于为ViewGroup的子View添加出场效果.
- LayoutAnimation可以通过在xml中实现,也可以通过java代码实现
//通过在布局文件中引用anim文件夹下定义的LayoutAnimation <layoutAnimation android:delay="500" android:animation="@anim/customanim" android:animationOrder="normal" xmlns:android="http://schemas.android.com/apk/res/android" /> <ListView * android:layoutAnimation="@anim/anim_layout"/>//通过LayoutAnimationController实现ViewGroup的布局动画 ListView lv; Animation anim = AnimationUtils.loadAnimation(this,R.anim.customanim); LayoutAnimationController controller = new LayoutAnimationController(anim); controller.setOrder(LayoutAnimationController.ORDER_NORMAL); lv.setLayoutAnimation(controller); - 无论是通过xml,还是通过java代码实现,本质都是通过LayoutAnimationController.见ViewGroup源码:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initViewGroup(); //解析xml中设置的属性 initFromAttributes(context, attrs, defStyleAttr, defStyleRes); } private void initFromAttributes( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { *** //解析到xml中设置了 android:layoutAnimation ,则生成LayoutAnimationController实例,执行 setLayoutAnimation case R.styleable.ViewGroup_layoutAnimation: int id = a.getResourceId(attr, -1); if (id > 0) { setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id)); } break; *** } public void setLayoutAnimation(LayoutAnimationController controller) { mLayoutAnimationController = controller; if (mLayoutAnimationController != null) { mGroupFlags |= FLAG_RUN_ANIMATION; } } - LayoutAnimation系统默认提供3种子元素动画执行顺序: 顺序执行/normal,逆向执行/reverse,随机顺序执行/random.
- LayoutAnimationController通过执行getTransformedIndex获取当下要为哪个索引值对应的Item执行动画.
- 通过重写getTransformedIndex,我们可以实现任意顺序的ViewGroup布局动画.
- Activity的切换效果
- overridePendingTransition(int enterAnim, int exitAnim)
- overridePendingTransition必须位于startActivity或finish后面,否则不生效.
- LayoutAnimation/布局动画
2. 帧动画
-
帧动画是按照顺序播放一组预先定义好的图片,类似于电影播放.
-
可以使用xml,也可以使用java代码创建
//在drawable文件夹下创建animation-list <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/ii1" android:duration="@android:integer/config_shortAnimTime" /> <item android:drawable="@mipmap/ii2" android:duration="@android:integer/config_shortAnimTime" /> <item android:drawable="@mipmap/ii3" android:duration="@android:integer/config_shortAnimTime" /> <item android:drawable="@mipmap/ii4" android:duration="@android:integer/config_shortAnimTime" /> <item android:drawable="@mipmap/ii5" android:duration="@android:integer/config_shortAnimTime" /> </animation-list> //在布局文件中引用 <View android:id="@+id/v1" android:layout_width="500px" android:layout_height="500px" android:background="@drawable/bg_animation_list" /> //在布局文件中引用后,依然需要在java代码中手动开启动画 View v1 = findViewById(R.id.v1); //1:XML中指定的AnimationDrawable需要手动开启 ((AnimationDrawable)v1.getBackground()).start();View v2 = findViewById(R.id.v2); AnimationDrawable anim = new AnimationDrawable(); anim.setOneShot(false); anim.addFrame(getResources().getDrawable(R.mipmap.ii1),200); anim.addFrame(getResources().getDrawable(R.mipmap.ii2),200); anim.addFrame(getResources().getDrawable(R.mipmap.ii3),200); anim.addFrame(getResources().getDrawable(R.mipmap.ii4),200); anim.addFrame(getResources().getDrawable(R.mipmap.ii5),200); v2.setBackgroundDrawable(anim); anim.start(); -
对于AnimationDrawable,即使是在drawable文件下定义的animation-list在布局文件xml中被引用,也需要在Java代码中手动开启才会执行动画!
-
帧动画在使用的图片较多,且图片较大时易出现OOM,要尽量规避.如果一定要用,可以使用如下优化方案
- Android AnimationDrawable帧动画OOM问题优化
- Android性能优化 | 帧动画OOM?优化帧动画之SurfaceView逐帧解析
- Android性能优化 | 大图做帧动画卡顿?优化帧动画之 SurfaceView滑动窗口式帧复用
//按照文章思路,自己简单写了1个 public class AnimationsContainer { private Context context = null; private int arraysId = -1; private ImageView targetImageView; private int delayPerFrame = -1; private Bitmap lastBitmap = null; private int index = -1; private int[] drawables = null; private Handler handler = null; private boolean started = false; private Thread gainBitmapThread = null; public AnimationsContainer(Context context, int arraysId, ImageView targetImageView, int delayPerFrame) { this.context = context; this.arraysId = arraysId; this.targetImageView = targetImageView; this.delayPerFrame = delayPerFrame; initHandler(); drawables = getData(this.arraysId); } private void initHandler() { handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { Bitmap bitmap = (Bitmap) msg.obj; targetImageView.setImageBitmap(bitmap); recycleCurrBitmap(); lastBitmap = bitmap; } }; } private void recycleCurrBitmap() { if (lastBitmap != null) { lastBitmap.recycle(); lastBitmap = null; } } private int getNextIndex() { int result = index; if (result < 0) { result = 0; } else { result = (result + 1) % drawables.length; } return result; } private Bitmap gainSpecificSizeBitmap(int index) { Bitmap bitmap = null; bitmap = BitmapFactory.decodeResource(context.getResources(), drawables[index]); return bitmap; } public void startAnimation() { started = true; if (gainBitmapThread == null) { gainBitmapThread = new Thread(new Runnable() { @Override public void run() { while (started) { index = getNextIndex(); Bitmap bitmap = gainSpecificSizeBitmap(index); Message message = Message.obtain(handler, 0, bitmap); handler.sendMessage(message); try { Thread.sleep(delayPerFrame); } catch (InterruptedException e) { e.printStackTrace(); } if (index >= drawables.length - 1) { stopAnimation(); break; } } } }); } gainBitmapThread.start(); } public void stopAnimation() { started = false; handler.removeCallbacksAndMessages(null); try { if (gainBitmapThread != null) { gainBitmapThread.stop(); gainBitmapThread = null; } } catch (Exception e) { gainBitmapThread = null; } index = -1; recycleCurrBitmap(); } /* <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="loading_anim"> <item>@drawable/img0</item> <item>@drawable/img1</item> <item>@drawable/img2</item> <item>@drawable/img3</item> <item>@drawable/img4</item> <item>@drawable/img5</item> <item>@drawable/img6</item> <item>@drawable/img7</item> <item>@drawable/img8</item> <item>@drawable/img9</item> <item>@drawable/img10</item> <item>@drawable/img11</item> <item>@drawable/img12</item> <item>@drawable/img13</item> <item>@drawable/img14</item> <item>@drawable/img15</item> <item>@drawable/img16</item> <item>@drawable/img17</item> <item>@drawable/img18</item> <item>@drawable/img19</item> <item>@drawable/img20</item> <item>@drawable/img21</item> <item>@drawable/img22</item> <item>@drawable/img23</item> <item>@drawable/img24</item> </string-array> </resources> */ /** * 从xml中读取帧数组 * * @param resId * @return */ private int[] getData(int resId) { TypedArray array = context.getResources().obtainTypedArray(resId); int len = array.length(); int[] intArray = new int[array.length()]; for (int i = 0; i < len; i++) { intArray[i] = array.getResourceId(i, 0); } array.recycle(); return intArray; } } //使用方法 AnimationsContainer controller = new AnimationsContainer(this,R.array.loading_anim,imageView,20); controller.startAnimation();
3. 属性动画
- 属性动画没有找到确切的定义.自己理解:
- 属性动画定义了一个指定类型的属性P的变化
- 属性P可以属于某一个对象,也可以脱离具体对象单独存在
- 属于某个对象的指定类型的属性P的变化: ObjectAnimator
- 脱离具体对象的指定类型的属性P的变化: ValueAnimator
- 属性动画按照使用场景分类
- ViewPropertyAnimator
- ObjectAnimator
- ValueAnimator
- ViewPropertyAnimator专门针对View做属性动画,可以操作的属性是指定的,数量有限,但是使用非常方便.
View target = **; tareget.animate().scaleX(1.50F).alpha(0.70F); - ObjectAnimator
- ObjectAnimator使用方式
CustomView cv = **; ObjectAnimator anim = ObjectAnimator.ofFloat(cv,"prop1",1.0F,2.0F); anim.start(); public class CustomView extends View{ //字段是 'prop2' public String prop2 = null; //方法是 'Prop1' public void setProp1(String prop){ this.prop2 = prop; invalidate(); } public void getProp1(){ return this.prop2; } *** } - 从上述示例可见ObjectAnimator特性
- 针对特定对象 target
- ObjectAnimator.of** 中的propertyName,并不需要是 target 的1个字段.只要存在propertyName对应的setter及getter方法即可.
- 使用Keyframe,PropertyValuesHolder可以实现'对复杂属性关系做动画'
- Keyframe用于把1个属性拆分为多段,对1个属性进行精细的控制
- 1个PropertyValuesHolder控制1个属性,使用多个PropertyValuesHolder创建ObjectAnimator实例,可以对多个属性同时做动画
public class CustomView extends View{ public float a1 = 0.0F; public float a2 = 0.0F; public float a3 = 0.0F; //a1,a2,a3的setter及getter方法 //setter方法会触发CustomView实例的重绘/invalidate } CustomView cv = ***; Keyframe kf1 = Keyframe.ofFloat(0.0F,0.0F); Keyframe kf2 = Keyframe.ofFloat(0.5F,120.0F); Keyframe kf2 = Keyframe.ofFloat(10,10.0F); PropertyValuesHolder holder1= PropertyValuesHolder.ofKeyframe("a1", kf1, kf2, kf3); *** PropertyValuesHolder holder2= PropertyValuesHolder.ofKeyframe("a2", kf4, kf5, kf6); *** PropertyValuesHolder holder3= PropertyValuesHolder.ofKeyframe("a3", kf7, kf8, kf9); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(cv, holder1,holder2,holder3); animator.start();
- ObjectAnimator使用方式
- ValueAnimator
- ValueAnimator是ObjectAnimator的父类.
- ValueAnimator用于声明1个脱离具体对象的指定类型属性的变化.
- ValueAnimator因为不与具体实例关联,所以需要在动画监听器回调中获取当下属性值,并执行具体逻辑,相对于ObjectAnimator稍微麻烦,但是也没有束缚,适用范围更广.
- ValueAnimator使用场景:
- 简单来说,ObjectAnimator实现不了的场景,交给ValueAnimator.
比如1个第三方库中的某个字段,没有setter方法,或想用1个动画控制多个对象的属性变化等等.
- 简单来说,ObjectAnimator实现不了的场景,交给ValueAnimator.