前言

Android中有很多有趣的简单几何元素自定义动画效果,比如圆形转圈这类加载动画都是很常见的,如果使用自定义的View来实现这种动画效果实际比较复杂而且很难在其他的View中复用。有种思路就是使用Drawable来实现动画效果,在把它作为View的前景或背景图设置上去,这样的动画效果就被封装到一个简单的Drawable,可以在不同的View之间重复使用。

属性动画实现

既然使用Drawable来实现动画下过,自然要使用自定义的Drawable对象,同时还要使用一个Animatable接口提供的方法来做动画控制实现。Drawable中需要实现的接口比较多,需要每个方法的功能都简单了解,Animatable比较简单这里不再赘述。

接口名 作用
draw(Canvas canvas) 在附加到的View里绘制Drawable内容
onBoundsChange(Rect bounds) 当View的大小位置改变时回调
setAlpha(int alpha) 设置当前Drawable的透明度
setColorFilter(ColorFilter colorFilter) 设置颜色滤镜
getOpacity() 返回Drawable的具体类型,可以是透明、半透明、全透明等


现在来实现一个简单的圆圈逐渐放大的动画,Drawable在创建之后还要附加到View中才能真正确定大小,所以需要在它的onBoundsChanged方法里定义各种初始变量和动画。

@Override
protected void onBoundsChange(Rect bounds) {super.onBoundsChange(bounds);width = bounds.width();height = bounds.height();Log.d(TAG, "width = " + width + ", height = " + height);if (valueAnimator != null && valueAnimator.isRunning()) {valueAnimator.cancel();}valueAnimator = ValueAnimator.ofInt(CommonUtils.dp2px(10), Math.min(width, height) / 2);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {// 对圆形的半径值做属性动画mRadius = (int) animation.getAnimatedValue();Log.d(TAG, "width = " + width + ", height = " + height);Log.d(TAG, "mRadius = " + mRadius);invalidateSelf();}});// 设置值动画的各种参数valueAnimator.setStartDelay(mDelay);valueAnimator.setDuration(1000);valueAnimator.setRepeatCount(ValueAnimator.INFINITE);valueAnimator.setRepeatMode(ValueAnimator.REVERSE);if (mIsStarted) {valueAnimator.start();}
}

在onBoundsChanged方法里获取了当前Drawable要展示的宽高,同时定义了圆形的半径从小到大的值动画效果,每次更新值的时候都会调用Drawable.invalidateSelf()方法来更新当前展示,也就是调用onDraw里的绘制逻辑。

public CircleDrawable() {paint = new Paint();paint.setAntiAlias(true);paint.setColor(Color.RED);paint.setStrokeWidth(CommonUtils.dp2px(2));paint.setStyle(Paint.Style.STROKE);mRadius = CommonUtils.dp2px(10);
}@Override
public void draw(@NonNull Canvas canvas) {canvas.drawCircle(width / 2, height / 2, mRadius, paint);
}

上面的draw代码里只是做了绘制一个圆的操作,随着半径不断的变化Drawable上展示的圆也不断的改变大小,这样就实现了简单的圆形大小不断变化的动画。Drawable可以单独使用也可以包装其他的Drawable来实现复杂的效果,比如ClipDrawable、StateListDrawable内部都可以使用BitmapDrawable,并且通过特殊的接口来实现内部BitmapDrawable的展示。这里定义一个包含多个圆形的Drawable,它会包含多个不同大小的圆形不断改变的动画效果。

public class MultiCircleDrawable extends Drawable implements Animatable, Drawable.Callback {private final int START_DELAY = 200;private CircleDrawable[] circleDrawables = new CircleDrawable[] {new CircleDrawable(),new CircleDrawable(),new CircleDrawable()};public MultiCircleDrawable() {for (int i = 0; i < circleDrawables.length; i++) {circleDrawables[i].setStartDelay(i * START_DELAY);circleDrawables[i].setCallback(this);}}....@Overridepublic void draw(@NonNull Canvas canvas) {for (CircleDrawable drawable : circleDrawables) {drawable.draw(canvas);}}@Overrideprotected void onBoundsChange(Rect bounds) {super.onBoundsChange(bounds);for (CircleDrawable drawable : circleDrawables) {drawable.onBoundsChange(bounds);}}@Overridepublic void setAlpha(int alpha) {for (CircleDrawable drawable : circleDrawables) {drawable.setAlpha(alpha);}}@Overridepublic void setColorFilter(@Nullable ColorFilter colorFilter) {for (CircleDrawable drawable : circleDrawables) {drawable.setColorFilter(colorFilter);}}@Overridepublic int getOpacity() {return PixelFormat.OPAQUE;}@Overridepublic void invalidateDrawable(@NonNull Drawable who) {invalidateSelf();}@Overridepublic void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {}@Overridepublic void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {}
}

我们知道Drawable只能依附在View对象上才能正常展示,MultiCircleDrawable会被依附到View里,它内部的CircleDrawable就不会被依附到View上,但是内部的CircleDrawable又需要通知View做更新操作,这时候就需要将MultiCircleDrawable来做内部CircleDrawable的回调对象,当它的invalidateDrawable被回调时调用invalidateSelf通知View对象做刷新操作。

Drawable.CallBack实现

public interface Callback {// 使当前的Drawable对象非法,最终导致Drawable依附的View重新绘制void invalidateDrawable(@NonNull Drawable who);// 调用Drawable相关的Runnable执行void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);// 取消Drawable相关的Runnable执行void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}

上面的Drawable.Callback接口是View实现的接口之一,View内部会处理回调发生时的逻辑,查看源码如下所示。

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {// View执行    @Overridepublic void invalidateDrawable(@NonNull Drawable drawable) {if (verifyDrawable(drawable)) {final Rect dirty = drawable.getDirtyBounds();final int scrollX = mScrollX;final int scrollY = mScrollY;invalidate(dirty.left + scrollX, dirty.top + scrollY,dirty.right + scrollX, dirty.bottom + scrollY);rebuildOutline();}}// 向主线程中post一个Runnable@Overridepublic void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {if (verifyDrawable(who) && what != null) {final long delay = when - SystemClock.uptimeMillis();if (mAttachInfo != null) {mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, what, who,Choreographer.subtractFrameDelay(delay));} else {getRunQueue().postDelayed(what, delay);}}}    // 从主线程中清除要执行Runnable@Overridepublic void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {if (verifyDrawable(who) && what != null) {if (mAttachInfo != null) {mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, what, who);}getRunQueue().removeCallbacks(what);}}
}

在Drawable源码中会直接调用Callback来实现刷新,调度任务和取消调度任务的操作,从实现的源代码中可以看出调用invalidateSelf就会回调Callback的invalidateDrawable,前面的MultiDrawable就实现了这个方法invalidateDrawable,而附加到View里的Drawable它的callback就是View回调的就是上面的View.invalidateDrawable,实现View的重新绘制,在View.onDraw里会调用Drawable.onDraw最终实现Drawable内容的刷新操作。

// Drawable内部实现
public void invalidateSelf() {final Callback callback = getCallback();if (callback != null) {callback.invalidateDrawable(this);}
}public void scheduleSelf(@NonNull Runnable what, long when) {final Callback callback = getCallback();if (callback != null) {callback.scheduleDrawable(this, what, when);}
}public void unscheduleSelf(@NonNull Runnable what) {final Callback callback = getCallback();if (callback != null) {callback.unscheduleDrawable(this, what);}
}

简单涟漪效果

涟漪效果其实就是用户点击View之后会出现圆形逐渐变大最后再消失的动画效果,有了前面的圆大小改变动画,这里再通过scheduleSelf和unscheduleSelf接口来实现动画效果。首先定一个一个Button它内部包含一个有涟漪效果的Drawable。

public class RippleButton extends AppCompatButton {private RippleDrawable mDrawable;public RippleButton(Context context) {this(context, null);}public RippleButton(Context context, AttributeSet attrs) {this(context, attrs, 0);}public RippleButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 自定义的涟漪效果DrawablemDrawable = new RippleDrawable();mDrawable.setCallback(this);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);// 在View大小改变的时候设置Drawable大小mDrawable.setBounds(0, 0, getWidth(), getHeight());}@Overrideprotected void onDraw(Canvas canvas) {// 绘制Drawable内容mDrawable.draw(canvas);super.onDraw(canvas);}@Overrideprotected boolean verifyDrawable(@NonNull Drawable who) {return mDrawable == who || super.verifyDrawable(who);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 拦截用户触摸事件mDrawable.onTouchEvent(event);super.onTouchEvent(event);return true;}
}

自定义Button实现非常简单,需要注意onSizeChange的时候需要通知Drawable当前View的尺寸发生变化,Drawable内部实现初始化操作,后面的onTouchEvent一定要返回true,这样ACTION_DOWN之后的ACTION_MOVE、ACTION_UP才会继续派发下来。

public class RippleDrawable extends Drawable {private Paint mPaint;private int mRadius;private int mWidth;private int mHeight;private int mMaxRadius;private int mPivotX;private int mPivotY;private int mMotionX;private int mMotionY;private float mProgress = 0.0f;private int mAlpha = 255;public RippleDrawable() {mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setDither(true);mPaint.setColor(Color.CYAN);mPaint.setStyle(Paint.Style.FILL);}// 绘制圆形@Overridepublic void draw(@NonNull Canvas canvas) {canvas.drawCircle(mPivotX, mPivotY, mRadius, mPaint);}// 记录当前Drawable的尺寸@Overrideprotected void onBoundsChange(Rect bounds) {super.onBoundsChange(bounds);mWidth = bounds.width();mHeight = bounds.height();mMaxRadius = Math.min(mWidth, mHeight);}@Overridepublic void setAlpha(int alpha) {mPaint.setAlpha(alpha);}@Overridepublic void setColorFilter(@Nullable ColorFilter colorFilter) {mPaint.setColorFilter(colorFilter);}@Overridepublic int getOpacity() {return PixelFormat.OPAQUE;}// 根据ACTION_DOWN展示圆心位置public void onTouchEvent(MotionEvent event) {switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:onTouchDown(event);break;case MotionEvent.ACTION_MOVE:onTouchMove(event);break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:onTouchUp(event);break;}}private void onTouchUp(MotionEvent event) {}private void onTouchMove(MotionEvent event) {}private void onTouchDown(MotionEvent event) {mMotionX = (int) event.getX();mMotionY = (int) event.getY();mRadius = 0;mProgress = 0.0f;mAlpha = 255;setAlpha(mAlpha);unscheduleSelf(mEnterRunnable);// 每10ms开始一次重绘scheduleSelf(mEnterRunnable, SystemClock.uptimeMillis() + 10);}private Interpolator interpolator = new AccelerateInterpolator();private Runnable mEnterRunnable = new Runnable() {@Overridepublic void run() {// 如果重绘完成,开始执行退出动画if (mProgress >= 1.0f) {unscheduleSelf(mExitRunnable);scheduleSelf(mExitRunnable, SystemClock.uptimeMillis() + 10);return;}// 不断增大圆形半径mProgress += 0.05f;mRadius = (int) (mMaxRadius * interpolator.getInterpolation(mProgress));mPivotX = mMotionX;mPivotY = mMotionY;invalidateSelf();scheduleSelf(mEnterRunnable, SystemClock.uptimeMillis() + 10);}};// 退出动画private Runnable mExitRunnable = new Runnable() {@Overridepublic void run() {int alpha = mAlpha - 10;if (alpha > 0) {// 从不透明变成完全透明实现Drawable内容不可见mAlpha = alpha;setAlpha(alpha);invalidateSelf();scheduleSelf(this, SystemClock.uptimeMillis() + 10);} else {mAlpha = 0;setAlpha(mAlpha);invalidateSelf();}}};
}

RippleDrawable内部onBoundsChange会在View的onSizeChange发生时被回调,会记录下当前Drawable展示的宽高和圆形最大的半径值,在draw方法中会根据轴心和半径绘制圆形。内部的mEnterRunnable表示用户按下时执行的圆形不断变大动画,mExitRunnable表示变大动画执行完成后圆形从不透明变成全透明最终消失的动画,两个动画都是通过Runnable的递归实现。

Drawable动画实现相关推荐

  1. android 动画之漂移,Android之自定义Drawable实现灵动的红鲤鱼动画(上篇)

    此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 由于整个绘制分析过程比较繁琐所以灵动的红鲤鱼 ...

  2. android开发 鱼动画,自定义Drawable实现灵动的红鲤鱼动画(上篇)

    此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 小鱼儿 由于整个绘制分析过程比较繁琐所以灵动 ...

  3. [转]自定义Drawable实现灵动的红鲤鱼动画(上篇)

    此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 小鱼儿 由于整个绘制分析过程比较繁琐所以灵动 ...

  4. 自定义Drawable实现灵动的红鲤鱼动画(上篇)

    此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 由于整个绘制分析过程比较繁琐所以灵动的红鲤鱼 ...

  5. 效果炸了,自定义Drawable实现灵动的红鲤鱼动画(上)

    Jics | 作者 承香墨影 | 校对 http://www.jianshu.com/p/3dd3d1524851 | 原文 一.前言 Hi,大家好,这里是承香墨影! 此篇中的小鱼动画是模仿国外一个大 ...

  6. 用surfaceView实现高性能动画

    "下面会用github上的一个开源drawable动画框架做性能上的对比" 引言 在Android上处理简单的动画,估计大家首先想到的是视图动画,补间动画,属性动画这些.再复杂一些 ...

  7. Android动画(一)-视图动画与帧动画

    项目中好久没用过动画了,所以关于动画的知识都忘光了.知识总是不用则忘.正好最近的版本要添加比较炫酷的动画效果,所以也借着这个机会,写博客来整理和总结关于动画的一些知识.也方便自己今后的查阅. Andr ...

  8. android动画帧率_Android动画进阶—使用开源动画库nineoldandroids

    前言 Android系统支持原生动画,这为应用开发者开发绚丽的界面提供了极大的方便,有时候动画是很必要的,当你想做一个滑动的特效的时候,如果苦思冥想都搞不定,那么你可以考虑下动画,说不定动画轻易就搞定 ...

  9. Android之动画精讲一:从setTranslationX谈属性动画和view动画的区别

    转载:http://blog.csdn.net/yanzi1225627/article/details/47850471 最近又用到了动画,决定把几次项目里用到的动画走过的弯路总结一下,顺便梳理下a ...

最新文章

  1. 在centos上通过yum直接安装最新版gcc和开发工具
  2. Linux下动态共享库加载时的搜索路径详解
  3. 2019-11-10 等价、相似、合同的一些概念
  4. zookeeper实现动态获取服务器列表代码示例(服务上下线监听/动态更新服务列表)
  5. iOS - OC SQLite 数据库存储
  6. 有理数取余(洛谷-P2613)
  7. eureka server启动后端口变为8080问题解决
  8. DP动态规划之01背包问题
  9. SMTP邮件服务器要求安全连接或客户端未通过身份验证的各个解决方案
  10. 射频电路学习之Smith圆图
  11. 练习java文档Matcher
  12. 聚合路由器的原理和应用
  13. 项目管理知识体系指南 (六)
  14. @Cacheable注解介绍
  15. 全靠这份Java知识点PDF大全,先睹为快
  16. Windows C盘清理方法
  17. 代码敲累了就来看看《PPT制作经验分享-发布版PPT》
  18. XENAPP 7.6 和 XENDESKTOP 7.6 初体验之四 创建桌面计算机目录
  19. javascript使用插件
  20. 数据库——MySQL(一)(数据库常用命令、数据类型、创建表与修改表结构、约束、约束修改添加)

热门文章

  1. 小米新一代大数据统计平台大公开
  2. 百度抓取诊断时 IP显示错误的解决办法。
  3. 重磅!百度网盘新规发布:将收回已获得的免费空间!网友炸裂了
  4. 硬件学习 软件Cadence day03 焊盘制作
  5. PbS包覆PbSe量子点|PbS/PbSeQDs|硒化铅包裹硒化铅量子点|PbSe核是6nm左右|Pbs壳层1-2nm
  6. VS2017打包安装包
  7. 美光科技股票基本分析:经济背景、行业分析财政状况(盈利、EBITDA、PPE、DA等)预测计算DCF...
  8. 数据看Kobe,请让我以这样的方式说再见
  9. 压缩空气储能研究(Matlab代码)
  10. ofd-editor ofd在线编辑器介绍