Android Drawable 简析

咕咚 于 2019-01-31 发布

Photo by Patrick Tomasso on Unsplash

Drawable 是开发中经常用到的一个概念,我们经常用它去设置 View 的背景,背景可以一个颜色值,也可以是一张资源图片,还可以是一个自定义的 Drawable等等。这篇文章就简单说下 Drawable 与 View 的关系,同时结合代码,简要分析一下 Drawable 如何作用于 View。

Drawable 介绍

官方介绍

A Drawable is a general abstraction for “something that can be drawn.” Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.

简单翻译下:

Drawable 是 “所有可绘制东西” 的一个抽象,大多数时候,我们只需要把各种不同类型的资源作为转化为 drawable,然后 View 会帮我们把它渲染到屏幕上。Drawable 类提供了一个通用 API,用于解析转化各种可视资源到 Canvas,跟 View 不一样,Drawable 不能接受任何事件以及用户交互

总而言之,Drawable 就是一个可绘制东西的抽象,相比 View,它更纯粹,就是用来做绘制相关事情的,它处理不了用户交互事件,也不需要处理,所有交互相关的事都是由 View 来完成,但是背景相关的事大都可以通过 Drawable 来完成。

一般的,我们要为 View 设置背景,可通过如下几种方式:

用颜色设置背景

通过 View 的 setBackgroundColor 方法可以设置颜色为 View 的背景。

button.setBackgroundColor(Color.YELLOW);

效果如下:

image

用自定义的 shape 设置背景

先用 xml 自定义一个圆角空心描边矩形 shape

<shape
     android:shape="rectangle">
    <corners android:radius="4dp"/>
    <solid android:color="#fff"/>
    <stroke android:color="#ef6f06" android:width="1dp"/>
</shape>

通过代码进行设置

button.setBackgroundResource(R.drawable.bk_normal);

效果如下: image

可以看到,给 View 设置背景 drawable 非常简单,具体通过如下的 API 实现背景设置:

但是设置的背景 Drawable 是如何在 View 上生效的,可能很多人没去思考过这个问题,这里简单分析下。

Drawable 如何作用于 View

Drawable 是一个抽象类,这里通过它的的几个抽象方法就能大概猜得出 Drawable 如何作用于 View,下面是 Drawable 的几个抽象方法:

public abstract void draw(@NonNull Canvas canvas);
public abstract void setAlpha(@IntRange(from=0,to=255) int alpha);
public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
public abstract @PixelFormat.Opacity int getOpacity();

可以看到这里有一个 draw 方法,并且参数中提供了 canvas 对象。

public abstract void draw(@NonNull Canvas canvas);

现在可以想象一下,View 通过 setBackground 方法为自己设置了一个 drawable 对象后,而 drawable 又有一个 draw 方法,那么 View 绘制自己的背景时,直接调用 drawable 对象的的 draw 方法,这个 draw 方法需要一个 canvas 对象,这里可直接把 View 的 Canvas 对象传递过去,那么 Drawable 就可以成功的把自己的绘制内容应用到 View 之上。

这个过程,相当于 View 把自己的背景绘制功能外包给了 Drawable 对象。

而且,这也是一种非常好的设计模式,View 负责测量自己大小,给自己指定位置,并绘制 View 前景 ,但是把自己的背景绘制外派给了更独立的 drawable 去做,从而做到了让自己更加轻量,现在 View 就成功的把背景绘制职责分配给了自己的 drawable 对象。

尽管上面只是想象,但事实上也确实如此。通过查看源码,在 View 中有一个私有方法 drawBackground,它的作用就是把 drawable 绘制在 canvas 上。

/**
 * Draws the background onto the specified canvas.
 * @param canvas Canvas on which to draw the background
 */
private void drawBackground(Canvas canvas) {
	final Drawable background = mBackground;
	if (background == null) {
		return;
	}
	setBackgroundBounds();
    //省略部分代码
	final int scrollX = mScrollX;
	final int scrollY = mScrollY;
	if ((scrollX | scrollY) == 0) {
        //调用 drawable 自己的 draw 方法,从而将绘制的功能移交到 drawable 类
		background.draw(canvas);
	} else {
		canvas.translate(scrollX, scrollY);
		background.draw(canvas);
		canvas.translate(-scrollX, -scrollY);
	}
}

Drawable 与 View 的关系

总结

Drawable 是一个抽象的概念,只要理解了它跟 View 的关系,其实 Drawable 的想象力会非常大。通过自定义 Drawable,可以在 Drawable 中完成各种绘制逻辑,自定义完成后,只需要让 View 调用 setBackground() 方法,把自定义的 Drawable 传递进去,这样就可以方便把自定义 Drawable 和 View 关联在一起。

之前写过一个转菊花的 Loading 效果,就是用自定义 Drawable 实现的,目前已开源在 github,感兴趣的去看看。

FlowerLoading: Android loading or progress view, just like iOS IndicatorView.