对 Drawable 的一些分析

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() 来为它设置绘制边界,不然是看不到绘制的结果的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值