Android提供的的动画系统有属性动画(Property Animation)、补间动画(View Animation)和帧动画(Drawable Animation)。现在比较常用的是属性动画,因为功能比较强大,正常我们直接对视图控件(View)进行属性动画的情况比较多,下面来介绍下动画效果Drawable的实现,相比View来讲Drawable更简单,使用起来也很方便。
一. 自定义Drawable
自定义动画Drawable只要继承Drawable并实现以下4个方法,同时实现Animatable接口:
public class CircleDrawable extends Drawable implements Animatable { @Override public void draw(Canvas canvas) { // 绘图 } @Override public void setAlpha(int alpha) { // 设置透明度 } @Override public void setColorFilter(ColorFilter colorFilter) { // 设置颜色过滤 } @Override public int getOpacity() { // 设置颜色格式 return PixelFormat.RGBA_8888; } @Override public void start() { // 启动动画 } @Override public void stop() { // 停止动画 } @Override public boolean isRunning() { // 判断动画是否运行 return false; } }在这几个方法中我们主要来处理Drawable的绘制,即 draw()方法,和自定义View一样。我们要实现动画效果,也实现Animatable接口,它的3个方法都是和动画相关,方法意图也很明显。下面来自定义一个圆圈逐渐扩散消失的效果:
/** * Created by long on 2016/7/2. * 圆圈Drawable */ public class CircleDrawable extends Drawable implements Animatable { private Paint mPaint; // 动画控制 private ValueAnimator mValueAnimator; // 扩散半径 private int mRadius; // 绘制的矩形框 private RectF mRect = new RectF(); // 动画启动延迟时间 private int mStartDelay; // 自定义一个扩散半径属性 Property<CircleDrawable, Integer> mRadiusProperty = new Property<CircleDrawable, Integer>(Integer.class, "radius") { @Override public void set(CircleDrawable object, Integer value) { object.setRadius(value); } @Override public Integer get(CircleDrawable object) { return object.getRadius(); } }; public int getRadius() { return mRadius; } public void setRadius(int radius) { mRadius = radius; } public CircleDrawable() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5); } @Override public void draw(Canvas canvas) { // 绘制圆圈 canvas.drawCircle(mRect.centerX(), mRect.centerY(), mRadius, mPaint); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } @Override public int getOpacity() { return PixelFormat.RGBA_8888; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mRect.set(_clipSquare(bounds)); if (isRunning()) { stop(); } // 计算最大半径 int maxRadius = (int) ((mRect.right - mRect.left) / 2); // 控制扩散半径的属性变化 PropertyValuesHolder radiusHolder = PropertyValuesHolder.ofInt(mRadiusProperty, 0, maxRadius); // 控制透明度的属性变化 PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0); mValueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, radiusHolder, alphaHolder); mValueAnimator.setStartDelay(mStartDelay); mValueAnimator.setDuration(1200); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 监听属性动画并进行重绘 invalidateSelf(); } }); // 设置动画无限循环 mValueAnimator.setRepeatMode(ValueAnimator.RESTART); mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); start(); } /** * 裁剪Rect为正方形 * @param rect * @return */ private Rect _clipSquare(Rect rect) { int w = rect.width(); int h = rect.height(); int min = Math.min(w, h); int cx = rect.centerX(); int cy = rect.centerY(); int r = min / 2; return new Rect( cx - r, cy - r, cx + r, cy + r ); } /************************************************************/ @Override public void start() { mValueAnimator.start(); } @Override public void stop() { mValueAnimator.end(); } @Override public boolean isRunning() { return mValueAnimator != null && mValueAnimator.isRunning(); } public void setAnimatorDelay(int startDelay) { mStartDelay = startDelay; } }整个流程还是比较简单,在构造函数里对画笔进行了初始化操作,复写onBoundsChange(Rect bounds)接口来获取图形边框参数,比如将Drawable设置给ImageView时,这里就能获取到ImageView的边框大小。在这方法里将边框裁剪为正方形,因为我们要做圆圈Drawable嘛。然后剩下的就是属性动画的处理了,这里自定义了一个扩散半径属性 mRadiusProperty,用来控制绘制圆圈的半径,除了对半径的控制外还有对透明度的控制。如果对自定义属性Property和PropertyValuesHolder不清楚可以看下这个: Androids属性动画PropertyValuesHolder的使用。
来看下给ImageView设置我们自定义的CircleDrawable的效果:
二. 包含多个动画Drawable
同理,实现包含多个动画的自定义Drawable也需要继承Drawable并实现Animatable接口,同时还要实现Drawable.Callback接口。先来看下Drawable.Callback的定义:
/*如果你想实现一个扩展子Drawable的动画drawable,那么你可以通过setCallBack(android.graphics.drawable.Drawable.Callback) *来把你实现的该接口注册到动画drawable中。可以实现对动画的调度和执行 */ public static interface Callback { /** * 当drawable重画时触发,这个点上drawable将被置为不可用 * @param 要求重画的drawable */ public void invalidateDrawable(Drawable who); /** * drawable可以通过该方法来安排动画的下一帧。可以仅仅简单的调用postAtTime(Runnable, Object, long) * 来实现该方法。参数分别与方法的参数对应 * @param who The drawable being scheduled. * @param what The action to execute. * @param when The time (in milliseconds) to run */ public void scheduleDrawable(Drawable who, Runnable what, long when); /** *可以用于取消先前通过scheduleDrawable(Drawable who, Runnable what, long when)调度的某一帧。 *可以通过调用removeCallbacks(Runnable,Object)来实现 * @param who The drawable being unscheduled. * @param what The action being unscheduled. */ public void unscheduleDrawable(Drawable who, Runnable what); }当我们需要重绘Drawable时,会调用 invalidateSelf()接口,来看下它是怎么操作的:
/** * Use the current {@link Callback} implementation to have this Drawable * redrawn. Does nothing if there is no Callback attached to the * Drawable. * * @see Callback#invalidateDrawable * @see #getCallback() * @see #setCallback(android.graphics.drawable.Drawable.Callback) */ public void invalidateSelf() { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } }需要如果给Drawable设置了 Drawable.Callback回调,就可以监听这个Drawable的重绘操作,并回调invalidateDrawable(Drawable who)方法。
好了,下面就可以来开始自定义带多个动画的Drawable,直接复用上面写的CircleDrawable,让多个CircleDrawable动画按顺序执行:
/** * Created by long on 2016/7/2. * 复数Circle的Drawable,需要实现Drawable.Callback接口 */ public class MultiCircleDrawable extends Drawable implements Animatable, Drawable.Callback { // 每个Drawable动画启动的间隔 private static final int EACH_CIRCLE_SPACE = 200; // CircleDrawable数组 private CircleDrawable[] mCircleDrawables; public MultiCircleDrawable() { mCircleDrawables = new CircleDrawable[] { new CircleDrawable(), new CircleDrawable(), new CircleDrawable() }; for (int i = 0; i < mCircleDrawables.length; i++) { // 设置动画启动延迟 mCircleDrawables[i].setAnimatorDelay(EACH_CIRCLE_SPACE * i); // 设置回调监听,当CircleDrawable发生重绘时就会调用 invalidateDrawable(Drawable who) 方法 mCircleDrawables[i].setCallback(this); } } @Override public void draw(Canvas canvas) { for (CircleDrawable drawable : mCircleDrawables) { // 分层绘制每个CircleDrawable int count = canvas.save(); drawable.draw(canvas); canvas.restoreToCount(count); } } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter colorFilter) { } @Override public int getOpacity() { return PixelFormat.RGBA_8888; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); for (CircleDrawable drawable : mCircleDrawables) { drawable.onBoundsChange(bounds); } } /************************************************************/ @Override public void start() { for (CircleDrawable drawable : mCircleDrawables) { drawable.start(); } } @Override public void stop() { for (CircleDrawable drawable : mCircleDrawables) { drawable.stop(); } } @Override public boolean isRunning() { for (CircleDrawable drawable : mCircleDrawables) { if (drawable.isRunning()) { return true; } } return false; } @Override public void invalidateDrawable(Drawable who) { // 需要重绘,子Drawable发生重绘会调用这个方法通知父Drawable,如果有设置Callback回调监听的话 invalidateSelf(); } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { } @Override public void unscheduleDrawable(Drawable who, Runnable what) { } }可以看到上面复用了三个CircleDrawable,并给它们设置了 动画启动延迟和 Drawable.Callback回调,并在回调方法invalidateDrawable(Drawable who)里也对当前的MultiCircleDrawable进行重绘,即调用invalidateSelf()。这样就完成了多个动画Drawable的定义,来看下使用效果:
这只是简单介绍自定义Drawable的使用,可以自己定义更多好看的动画效果,以上的源代码:DrawableSample。
自定义Drawable的运用可以参考这个:实现360手机助手TabHost的波纹效果