说到Drawable肯定会第一时间想到图片资源,可能有人会认为Drawable就是图片。Drawble从字面上说就是“可绘制的”,google文档的描述:“A Drawable is a general abstraction for “something that can be drawn.””,Drawable其实就是为那些”可以被绘制的’’提供的抽象类。
- 哪些东西是”can be drawn”?
- 绘制在哪里的?
一、Drawble是干嘛的?
看下android原生的Drawable和Canvas提供了的绘图的接口:android.graphics.drawable
android.graphics.Canvas。Canvas提供了一系列的接口去实现绘制想要的图形、图片、颜色或者其他something,Drawable就是为了这些”something”提供的。
二、如何自定义Drawable?如何简化布局?
相信一定见过类似这样的布局,在Android系统中如何把想要的图形绘制处理,一般有两种方式:
- 把你想要的视图简单的定义到布局文件中,系统读取布局文件绘制视图
- 把想要的图形通过canvas的绘图接口直接进行绘制
在第二种方式中,你需要通过比如Drawable,比如自定义的View的onDraw()方法,或者调用Canvas的draw…()方法,而View的方式实际上也是Canvas绘制的方式,View也可以看做是对Canvas绘图接口的包装。而直接绘制到Canvas上的方式也单单是这些,在主线程上你可以通过调用View的invalidate()方法触发绘制,在onDraw()方法处理绘图的回调。另一种非主线程绘制的方式就是SurfaceView了。
通过Drawable绘制想要的图形是个不错的选择,Drawable是一个抽象类,draw()方法提供了绘制的Canvas,invalidateSelf()方法提供了重绘的可能。Drawable的抽象方法分别是:
**draw(Canvas canvas)**将想要的东西绘制到这个canvas对象上
setAlpha(int alpha) 为drawable指定一个alpha值,0代表全透明,255代表全不透明。
setColorFilter(ColorFilter cf) 为drawable指定一个颜色过滤器
getOpacity() 获取透明度
实现自定义的Drawable只需要继承Drawable.java,并且在draw方法里通过canvas绘制需要的图形就可以了,听起来很简单。无论是一组图片、一条曲线还是一个色块,所有你想要的图形都可通过canvas参数绘制上去。举个例子,如何使用Drawable简化上图所示的网格布局?假设你需要在网格布局里显示不同的图片,就可以把场景微信朋友群组的图标。当然你可以使用view布局来做,当然也可以使用canvas方式。根据Drawable添加的顺序依次确定每个图片的坐标位置,在draw()方法里根据每个图片的坐标位置和图片显示位置绘制到canvas上。
1 | @Override |
三、让Drawable动起来怎么样?
- 首先看下Drawable的Canvas从哪里来
一般使用ImageView显示drawable,看下ImageView的onDraw(Canvas canvas),可以看到Drawable在ImageView里的绘制发生在View的onDraw回调里,根据scaleType和padding确定绘制的方式和位置。
1 | protected void onDraw(Canvas canvas) { |
View绘制Background Drawable在draw(Canvas canvas)方法,并在setBackgroundDrawable时会执行background.setCallback(this)操作。
1 | // Step 1, draw the background, if needed |
- 再看Drawable如何重绘,
Drawable是依赖Canvas绘制的,查看Drawable源码发现调用invalidateSelf()方法需要获取Callback,在Callback是空的情况下无法重绘Drawable。那么在View里如何实现Drawable的重绘,显然需要先设置Drawable的重绘回调,View.java实现了Drawable.Callback接口,在View里调用setImageDrawable()方法里会首先设置当前View为Drawable的回调。ImageView调用invalidate触发重绘,回调ImageView的onDraw()方法,完成Drawable的重绘。1
2
3
4
5
6
7
8
9
10
11
12public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
public static interface Callback {
public void invalidateDrawable(Drawable who);
public void scheduleDrawable(Drawable who, Runnable what, long when);
public void unscheduleDrawable(Drawable who, Runnable what);
}了解了Drawable绘制的画布和重绘的方法,如何实现动画的Drawable只需要按时重绘Drawable就可以了,用动画控制重绘效果。具体实现可以看https://github.com/Ivonhoe/FancyDrawable,现在看如何实现我在贝赛尔插值器里说过的BallsLine进度条效果,只需要在每次重绘的回调里动态的控制每个小球点的位置就可以了,需要的动画插值器参数可以参考Web代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Override
public void invalidateDrawable(Drawable dr) {
if (dr == mDrawable) {
/* we invalidate the whole view in this case because it's very
* hard to know where the drawable actually is. This is made
* complicated because of the offsets and transformations that
* can be applied. In theory we could get the drawable's bounds
* and run them through the transformation and offsets, but this
* is probably not worth the effort.
*/
invalidate();
} else {
super.invalidateDrawable(dr);
}
}
protected static final long FRAME_DURATION = 1000 / 60;
protected Runnable mUpdater = new Runnable() {
@Override
public void run() {
onFrame();
scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
invalidateSelf();
}
};
@Override
public void start() {
if (!isRunning()) {
mIsRunning = true;
// start
onStart();
scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
invalidateSelf();
}
}
@Override
public void stop() {
if (isRunning()) {
mIsRunning = false;
unscheduleSelf(mUpdater);
//stop
onStop();
}
}
五、参考文档
http://developer.android.com/reference/android/graphics/drawable/Drawable.html
http://developer.android.com/guide/topics/graphics/2d-graphics.html
http://wiresareobsolete.com/2012/12/textdrawable-draw-some-text/
http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/
http://antoine-merle.com/blog/2013/11/12/make-your-progressbar-more-smooth/
六、Github
https://github.com/Ivonhoe/FancyDrawable