Drawable动画实现
前言
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动画实现相关推荐
- android 动画之漂移,Android之自定义Drawable实现灵动的红鲤鱼动画(上篇)
此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 由于整个绘制分析过程比较繁琐所以灵动的红鲤鱼 ...
- android开发 鱼动画,自定义Drawable实现灵动的红鲤鱼动画(上篇)
此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 小鱼儿 由于整个绘制分析过程比较繁琐所以灵动 ...
- [转]自定义Drawable实现灵动的红鲤鱼动画(上篇)
此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 小鱼儿 由于整个绘制分析过程比较繁琐所以灵动 ...
- 自定义Drawable实现灵动的红鲤鱼动画(上篇)
此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 由于整个绘制分析过程比较繁琐所以灵动的红鲤鱼 ...
- 效果炸了,自定义Drawable实现灵动的红鲤鱼动画(上)
Jics | 作者 承香墨影 | 校对 http://www.jianshu.com/p/3dd3d1524851 | 原文 一.前言 Hi,大家好,这里是承香墨影! 此篇中的小鱼动画是模仿国外一个大 ...
- 用surfaceView实现高性能动画
"下面会用github上的一个开源drawable动画框架做性能上的对比" 引言 在Android上处理简单的动画,估计大家首先想到的是视图动画,补间动画,属性动画这些.再复杂一些 ...
- Android动画(一)-视图动画与帧动画
项目中好久没用过动画了,所以关于动画的知识都忘光了.知识总是不用则忘.正好最近的版本要添加比较炫酷的动画效果,所以也借着这个机会,写博客来整理和总结关于动画的一些知识.也方便自己今后的查阅. Andr ...
- android动画帧率_Android动画进阶—使用开源动画库nineoldandroids
前言 Android系统支持原生动画,这为应用开发者开发绚丽的界面提供了极大的方便,有时候动画是很必要的,当你想做一个滑动的特效的时候,如果苦思冥想都搞不定,那么你可以考虑下动画,说不定动画轻易就搞定 ...
- Android之动画精讲一:从setTranslationX谈属性动画和view动画的区别
转载:http://blog.csdn.net/yanzi1225627/article/details/47850471 最近又用到了动画,决定把几次项目里用到的动画走过的弯路总结一下,顺便梳理下a ...
最新文章
- 在centos上通过yum直接安装最新版gcc和开发工具
- Linux下动态共享库加载时的搜索路径详解
- 2019-11-10 等价、相似、合同的一些概念
- zookeeper实现动态获取服务器列表代码示例(服务上下线监听/动态更新服务列表)
- iOS - OC SQLite 数据库存储
- 有理数取余(洛谷-P2613)
- eureka server启动后端口变为8080问题解决
- DP动态规划之01背包问题
- SMTP邮件服务器要求安全连接或客户端未通过身份验证的各个解决方案
- 射频电路学习之Smith圆图
- 练习java文档Matcher
- 聚合路由器的原理和应用
- 项目管理知识体系指南 (六)
- @Cacheable注解介绍
- 全靠这份Java知识点PDF大全,先睹为快
- Windows C盘清理方法
- 代码敲累了就来看看《PPT制作经验分享-发布版PPT》
- XENAPP 7.6 和 XENDESKTOP 7.6 初体验之四 创建桌面计算机目录
- javascript使用插件
- 数据库——MySQL(一)(数据库常用命令、数据类型、创建表与修改表结构、约束、约束修改添加)
热门文章
- 小米新一代大数据统计平台大公开
- 百度抓取诊断时 IP显示错误的解决办法。
- 重磅!百度网盘新规发布:将收回已获得的免费空间!网友炸裂了
- 硬件学习 软件Cadence day03 焊盘制作
- PbS包覆PbSe量子点|PbS/PbSeQDs|硒化铅包裹硒化铅量子点|PbSe核是6nm左右|Pbs壳层1-2nm
- VS2017打包安装包
- 美光科技股票基本分析:经济背景、行业分析财政状况(盈利、EBITDA、PPE、DA等)预测计算DCF...
- 数据看Kobe,请让我以这样的方式说再见
- 压缩空气储能研究(Matlab代码)
- ofd-editor ofd在线编辑器介绍