分析昨天的博客 span从入门到精通 第三方工具类GifDrawable 发现有个知识点有必要先梳理下要不大家可能看着博客都是懵逼的,这个知识点就是自定义drawable。
看看效果吧
首先我们先分析下源码里面drawable 是怎么被调用的我们先看看view类 的setBackgroundDrawable 这个方法
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
if (background == mBackground) {
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
/*
* Regardless of whether we're setting a new background or not, we want
* to clear the previous drawable. setVisible first while we still have the callback set.
*/
if (mBackground != null) {
if (isAttachedToWindow()) {
mBackground.setVisible(false, false);
}
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
if (background != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
resetResolvedDrawablesInternal();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
resetResolvedPaddingInternal();
switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
mUserPaddingLeftInitial = padding.right;
mUserPaddingRightInitial = padding.left;
internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
mUserPaddingLeftInitial = padding.left;
mUserPaddingRightInitial = padding.right;
internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
}
mLeftPaddingDefined = false;
mRightPaddingDefined = false;
}
// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBackground == null
|| mBackground.getMinimumHeight() != background.getMinimumHeight()
|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {
requestLayout = true;
}
// Set mBackground before we set this as the callback and start making other
// background drawable state change calls. In particular, the setVisible call below
// can result in drawables attempting to start animations or otherwise invalidate,
// which requires the view set as the callback (us) to recognize the drawable as
// belonging to it as per verifyDrawable.
mBackground = background;
if (background.isStateful()) {
background.setState(getDrawableState());
}
if (isAttachedToWindow()) {
background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
applyBackgroundTint();
// Set callback last, since the view may still be initializing.
background.setCallback(this);
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
} else {
/* Remove the background */
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
&& (mDefaultFocusHighlight == null)
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
/*
* When the background is set, we try to apply its padding to this
* View. When the background is removed, we don't touch this View's
* padding. This is noted in the Javadocs. Hence, we don't need to
* requestLayout(), the invalidate() below is sufficient.
*/
// The old background's minimum size could have affected this
// View's layout, so let's requestLayout
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate(true);
invalidateOutline();
}
这里面的意思大致分析一下就是首先判断设置的drawable是否跟以前的一样如果一样则不需要重新设置,如果旧的background不为空则清理下旧的background的数据,之后设置background的state,visible,callback等属性,最终调用requestLayout这个方法重新绘制布局也就是会走layout跟draw等方法,系统会在draw方法里面调用drawBackground这个处理我们来看drawBackground这个方法里面做了些什么处理
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
这里我们只关注一句话background.draw(canvas);即当我们在绘制内容之前会调用drawable的draw方法绘制背景,因此drawable里面的draw方法是一个十分关键的代码它会将我们需要的图片文字等内容绘制到界面中。好了源码先分析到这里我们还是先看看基本使用吧。
好了废话先不多说了先看实现代码吧我们首先定义一个drawable类。(一个极其简单的类)
public class TestDrawable extends Drawable {
private Paint paint;//画笔
private int mWidth = 300;//图片宽与高的最小值
public TestDrawable() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.FILL);
}
public void setmWidth(int width){
this.mWidth=width;
invalidateSelf();//更新设置
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, paint);
}
@Override
public void setAlpha(int i) {
paint.setAlpha(i);
invalidateSelf();//更新设置
}
@Override
public int getIntrinsicHeight() {
return mWidth;
}
@Override
public int getIntrinsicWidth() {
return mWidth;
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
invalidateSelf();//更行设置
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
看到了吧是不是有一种恍然大明白的感觉
我们的设置极其简单许多都是固定模式化的东西
1.初始化paint这个不用我多说了吧不知道的自觉面壁去
2.setAlpha中设置paint的alpha
3.getIntrinsicHeight getIntrinsicWidth当布局为wrap_content的时候我们要画的内容的宽高
4.setColorFilter调用paint的setColorFilter处理
5.getOpacity一班返回PixelFormat.TRANSLUCENT即可
6.最最核心的处理draw方法这里我们画了一个圆大家也可以画其他图形,图片等处理
7.额外添加一个方法setmWidth重新定义控件的宽度这里要注意了invalidateSelf这个方法调用会重新绘制该布局
这下明白了吧下面就是外部的调用
TestDrawable drawable = new TestDrawable();
ImageView iv_main = findViewById(R.id.iv_main);
iv_main.setImageDrawable(drawable);
drawable.setmWidth(50);
一句iv_main.setImageDrawable将我们画的drawable中的圆画到了界面上是不是很神奇
当然大家如果需要做动画的话可以调用invalidateSelf不断改变我们画的内容
如果大家还是不太明白可以看看画图片是怎么处理的代码如下
public class TestDrawable extends Drawable {
private Paint paint;//画笔
private int mWidth;//图片宽与高的最小值
private Bitmap bitmap;//位图
public TestDrawable(Context context, int resID) {
this(BitmapFactory.decodeResource(context.getResources(), resID));
}
public TestDrawable(Bitmap bitmap) {
this.bitmap = bitmap;
paint = new Paint();
paint.setAntiAlias(true);//抗锯齿
paint.setDither(true);//抖动,不同屏幕尺的使用保证图片质量
///位图渲染器
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(bitmapShader);
mWidth = Math.min(bitmap.getWidth(), bitmap.getHeight());
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, paint);
}
@Override
public void setAlpha(int i) {
paint.setAlpha(i);
invalidateSelf();//更新设置
}
@Override
public int getIntrinsicHeight() {
return bitmap.getHeight();
}
@Override
public int getIntrinsicWidth() {
return bitmap.getWidth();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
invalidateSelf();//更行设置
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
代码也不多希望大家看完博客后能对drawable有个更深入的认识