自定义控件——Canvas与图层

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43847987/article/details/102569777

获取Canvas对象的方法

方法一:重写onDraw(),dispatchDraw()函数

这两个函数在我们自定义view时是非常常用的,函数结构如下:

//其中的Canvas对象是View的Canvas对象,利用这个对象画图,效果会直接反映在View中
protected void onDraw(Canvas canvas){//该函数用于绘制试图本身
	super.onDraw(canvas);
}

protected void dispatchDraw(Canvas canvas){//用于绘制子视图
	super.dispatchDraw(canvas);
}

无论是View还是ViewGroup两者的调用顺序都是onDraw()—>dispatchDraw()。
在ViewGroup中,当它有背景时,就会调用onDraw()函数;否则就会跳过onDraw(),直接调用dispatchDraw()函数。所以在ViewGroup中绘图时,往往会重写dispatchDraw().

总结:在绘制View时,需要重写onDraw()函数;在绘制ViewGroup时,需要重写dsipatchDraw()函数.

方法二:使用Bitmap创建

构建方法:

Canvas canvas = new Canvas(bitmap);
等同于:
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);

bitmap可以从图片中加载,也可以自行创建

//1.新建一个空白bitmap
Bitmap bmp = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);

//2.从图片中加载
Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null);

在onDraw()函数中使用
使用bitmap创建的canvas,在其上面绘制的图像不会画到View中,它会保存在Bitmap上。所以使用这种方式画图时,需要使用onDraw(Canvas canvas)函数中传入的Canvas再画一遍Bitmap。
如下代码:

public class BitamapCanvasView extends View {

    private Bitmap mBmp;
    private Paint mPaint;
    private Canvas mBmpCanvas;

    public BitamapCanvasView(Context context) {
        super(context);
    }

    public BitamapCanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint=new Paint();
        mPaint.setColor(Color.BLACK);
        //创建一个空白的Bitmap
        mBmp=Bitmap.createBitmap(500,500,Bitmap.Config.ARGB_8888);
        mBmpCanvas=new Canvas(mBmp);
    }

    public BitamapCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setTextSize(50);
        //mBmpCanvas时我们使用Bitmap创建的,使用该canvas绘制文字,文字只会保存再这个bitmap中,不会画在View上。
        mBmpCanvas.drawText("一二三木头人",0,100,mPaint);
        //使用onDraw()的canvas将bitmap在绘制一边,就会出现在View上了
        canvas.drawBitmap(mBmp,0,0,mPaint);
    }
}

方法三:调用SurfaceHolder.lockCanvas()函数
在使用SurfaceView时,当调用SurfaceHolder.lockCanvas()函数时,也会创建Canvas对象。

图层和画布

保存和恢复画布的函数除了save()和restore()函数外,还有以下函数

savelayer()函数

构造函数如下所示:

/**
*指定保存矩形区域的Canvas内容
*RectF bounds: 要保存的区域所对的矩形对象
*int saveFlags: ALL_SAVE_FLAG,MATRIX_SAVE_FLAG等等取值 
*/
public int saveLayer(RectF bounds,Paint paint,int saveFlags)
public int saveLayer(float left,float top,float right,float bottom.Paint paint,int saveFlags)

saveLayer()使用示例:


public class XfermodeView extends View {
    private int width=400;
    private int height=400;
    private Bitmap dstBmp;
    private Bitmap srcBmp;
    private Paint mPaint;
    public XfermodeView(Context context) {
        super(context);
    }

    public XfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //设置禁用硬件加速器
        setLayerType(View.LAYER_TYPE_SOFTWARE,null);
        //源图像
        srcBmp=makeSrc(width,height);
        //目标图像
        dstBmp=makeDst(width,height);
        mPaint=new Paint();
    }

    /**
     * @param width
     * @param height
     * @return
     * 用于绘制目标图像,一个圆形图案
     */
    private Bitmap makeDst(int width, int height) {
        Bitmap bm=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
        Canvas c=new Canvas(bm);
        Paint p=new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0,0,width,height),p);
        return bm;
    }

    /**
     * @param width
     * @param height
     * @return
     * 用于绘制源图像,一个方形图案
     */
    private Bitmap makeSrc(int width, int height) {
        Bitmap bm=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
        Canvas c=new Canvas(bm);
        Paint p=new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFF66AAFF);
        c.drawRect(0,0,width,height,p);
        return bm;
    }

    public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //原始画布,底层绘制为绿色
        canvas.drawColor(Color.GREEN);
        //使用saveLayer函数,生成一块全新的画布,这块画布的大小就是我们指定的所要保存区域的大小
        int layerId=canvas.saveLayer(0,0,width*2,height*2,mPaint,Canvas.ALL_SAVE_FLAG);
        //在使用setXfermode之前绘制,画布上所有的内容都作为目标图像
        canvas.drawBitmap(dstBmp,0,0,mPaint);
        //Mode.SRC_IN模式:在处理原图像的时候,以显示源图像为主,
        // 在相交时利用目标图像的透明度来改变源图像的透明度和饱和度;
        // 当目标图像的透明度为0时,源图像就完全不显示
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(srcBmp,width/2,height/2,mPaint);
        mPaint.setXfermode(null);
        //在这一步之前的所有绘图操作都会画在saveLayer生成的画布上,在执行了这一步之后,所执行的画图操作才会出现在原始画布上。
        canvas.restoreToCount(layerId);
    }
}

运行截图如下:
在这里插入图片描述

将其中使用saveLayer的代码注释掉结果如下:

在这里插入图片描述
我们发现该图显示出的结果与我们所想的并不一样,因为在使用setXfermode这个函数时,我们知道当设置为Mode.SRC_IN模式的时候,在处理原图像的时,以显示原图像为主(图中的矩形为源图像),在相交时利用目标图像(上图中的圆形)的透明度来改变源图像的透明度和饱和度;当目标图像的透明度为0(即为全透明时)时,源图像就完全不显示。
在去掉savelayer的相关操作后我们发现,第一个结果是对的,因为除与圆相交以外的区域透明度都是0;而第二个结果就不是我们所想的了,源图像怎么全部显示出来了??????
别急咱们一步步来讲讲使用saveLayer()函数的绘图流程和不使用saveLayer()函数时的绘图流程:

1.使用saveLayer()函数的绘图流程
在调用saveLayer函数时,会生成一个全新的画布(Bitmap),这块画布的大小就是我们指定的所要保存区域的大小。新生成的画布时是透明的,再调用saveLayer函数后所有的绘图操作都是在这块画布上进行的。
在使用Xfermode画源图像时,会把之前画布上所有的内容都作为目标图像;而在调用saveLayer之上只有dstBmp对应的圆形,所以除于圆形相交之外的位置都是空白像素(透明度都为0),所以除相交的区域其他区域源图像都不显示。所以最后我们只看到1/4的圆弧区域为蓝色。
以下是Xfermode的合成过程:
在这里插入图片描述

2.不使用saveLayer函数的绘图流程
在去掉savelayer就不会创建新的画布了,所有的绘图操作都在原始画布上进行,由于原始画布的背景为绿色,使用Xfermode画源图像时,dstBmp对应的圆形直接画在了原始画布上,所以目标画布上是没有透明像素的,所以最后就出现第二种情况,合成过程:
在这里插入图片描述

画布与图层

  • 图层(Layer):每次调用canvas.drawXXX系列函数,都会生成一个图层专门来绘制这个图形。
  • 画布(Bitmap):每块画布都是一个Bitmap,所有的图像都是画在这个Bitmap上的。
  • Canvas:Canvas是画布的表现形式,我们所要绘制的任何东西都是利用Canvas来实现的。

猜你喜欢

转载自blog.csdn.net/weixin_43847987/article/details/102569777
今日推荐