Drawable
在日常开发中经常用到,我们经常用它来给 View
设置背景图片,它到底是怎么做到的呢?这篇就带大家去了解一下
先看个例子
我写过一篇 Android绘制圆形图片的3个方法 ,这里就用里面的一个demo来用下:
public class CircleHeadView extends View {
private Paint mPaint;
....
{
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//Shader.TileMode.CLAMP为拉伸图片铺满
BitmapShader bitmapShader = new BitmapShader(getBitmap(dp2px(100))
, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(bitmapShader);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制一个圆形,圆形的颜色就是我们指定的图片
canvas.drawCircle(dp2px(50), dp2px(50), dp2px(50), mPaint);
}
private Bitmap getBitmap(int width) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.bg_header, options);
options.inJustDecodeBounds = false;
//设置位图的屏幕密度,即每英寸有多少个像素
options.inDensity = options.outWidth;
//设置位图被画出来时的目标像素密度
//与options.inDensity配合使用,可对图片进行缩放
options.inTargetDensity = width;
return BitmapFactory.decodeResource(getResources(), R.drawable.bg_header, options);
}
/**
* 将dp或dp值转换为px值,保证尺寸大小不变
*/
private int dp2px(float dpValue) {
float scale = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue
, Resources.getSystem().getDisplayMetrics());
return (int) (scale + 0.5f);
}
}
这样子就可以将一个本地图片绘制成圆形来显示,但这里我们其实可以将绘制相关的都抽取出来的:
public class CircleHead {
private Paint mPaint;
public CircleHead() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
public void setBitmap(Bitmap bitmap) {
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP
, Shader.TileMode.CLAMP);
mPaint.setShader(bitmapShader);
}
public void draw(@NonNull Canvas canvas) {
//绘制一个圆形,圆形的颜色就是我们指定的图片
canvas.drawCircle(dp2px(50), dp2px(50), dp2px(50), mPaint);
}
}
public class CircleHeadView extends View {
private CircleHead mHead;
....
{
mHead = new CircleHead();
mHead.setBitmap(getBitmap(dp2px(100));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制一个圆形,圆形的颜色就是我们指定的图片
mHead.draw(canvas);
}
//.........
}
这样子就搞定了,运行效果和原来是一样的:
其实,这个就是 Drawable
的原理了,Drawable
其实就是 对 Canvas
进行指定绘制操作的抽象类,可以说,它只是一个绘制规则而已,下面我们看下 Drawable 的源码,会有更深刻的理解
Drawable的分析
Drawable 的源码很长,但是这里我们只看一些关键性的地方:
public abstract class Drawable {
/**
* 指定绘制矩形边界区域,在draw方法调用时用到其设置的值
* 不设置默认边界均为0,也就是Drawable的绘制区域为0,就不会进行绘制了
*/
public void setBounds(int left, int top, int right, int bottom) {
}
/**
* 返回一个setBounds设置的边界 Rect
* 如果没设置默认 Rect 里的值都是 0
*/
public final Rect getBounds() {
}
/**
* 一个回调,调用它可以触发View的invalidate方法
*/
public void invalidateSelf() {
}
/**
* 设置Drawable的透明度
* 我们自定义Drawable一般都需要定义画笔,用这个值来设置画笔的透明度即可
*/
public abstract void setAlpha(int alpha);
/**
* 设置Drawable滤镜效果
* 我们自定义Drawable一般都需要定义画笔,就在这里给画笔设置即可
*/
public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
/**
* 返回当前Drawable透明或者半透明或者不透明等,默认不清楚时直接返回TRANSLUCENT即可
* {@link android.graphics.PixelFormat}:
* {@link android.graphics.PixelFormat#UNKNOWN},
* {@link android.graphics.PixelFormat#TRANSLUCENT},
* {@link android.graphics.PixelFormat#TRANSPARENT}, or
* {@link android.graphics.PixelFormat#OPAQUE}.
*/
public abstract int getOpacity();
}
Drawable
里面的源码非常多,这里只是一部分,不过可以满足我们一大部分的开发需要了,我们将上面的 CircleHead
修改一下:
public class CircleHeadDrawable extends Drawable {
private Paint mPaint;
public CircleHeadDrawable(Bitmap bitmap) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP
, Shader.TileMode.CLAMP);
mPaint.setShader(bitmapShader);
//设置Drawable的边界
setBounds(0,0,bitmap.getWidth(),bitmap.getHeight());
}
@Override
public void draw(@NonNull Canvas canvas) {
//getBounds获取Drawable的边界
Rect rect = getBounds();
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
canvas.drawCircle(width >> 1, height >> 1, Math.min(width >> 1, height >> 1), mPaint);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
ok,封装好后就可以直接使用了:
private View mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mView = findViewById(R.id.view);
mView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mView.getViewTreeObserver().removeOnPreDrawListener(this);
CircleHeadDrawable drawable =
new CircleHeadDrawable(getBitmap(mView.getWidth()));
mView.setBackground(drawable);
return false;
}
});
}
运行结果和之前是一样的:
可以看出,Drawable
使用是非常灵活的
总结
Drawable
就是一系列绘制规则的抽象类,它里面的这个规则可以是 Bitmap
的绘制,也可以是其他符合你需求的绘制规则
而正因为 Drawable
只是个绘制规则的抽象类,因此在它的 draw()
被调用前,需要先调用
setBounds()
来为它设置绘制边界,不然是看不到绘制的结果的