自定义控件之绘图篇:Canvas与图层(二)

时间:2023-02-13 18:36:21

一、FLAG的具体意义

1、FLAG概述

有关save系列函数,在canvas中总共有如下几个:
public int save()  
public int save(int saveFlags)  
public int saveLayer(RectF bounds, Paint paint, int saveFlags)  
public int saveLayer(float left, float top, float right, float bottom,Paint paint, int saveFlags)  
public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)  
public int saveLayerAlpha(float left, float top, float right, float bottom,int alpha, int saveFlags)  
这段我们先关注前四个,save的两个函数和saveLayer的两个函数;我们知道他们两个不同之处在于saveLayer会新建一个画布,而save函数则不会新建画布;它们都具有Flag标识,这些Flag标识的意义和使用范围如下:
FLAG 意义 适用范围
ALL_SAVE_FLAG 保存所有的标识 save()、saveLayer()
MATRIX_SAVE_FLAG 仅保存canvas的matrix数组 save()、saveLayer()
CLIP_SAVE_FLAG 仅保存canvas的当前大小 save()、saveLayer()
HAS_ALPHA_LAYER_SAVE_FLAG 标识新建的bmp具有透明度,在与上层画布结合时,透明位置显示上图图像,与FULL_COLOR_LAYER_SAVE_FLAG冲突,若同时指定,则以HAS_ALPHA_LAYER_SAVE_FLAG为主 saveLayer()
FULL_COLOR_LAYER_SAVE_FLAG 标识新建的bmp颜色完全独立,在与上层画布结合时,先清空上层画布再覆盖上去 saveLayer()
CLIP_TO_LAYER_SAVE_FLAG 在保存图层前先把当前画布根据bounds裁剪,与CLIP_SAVE_FLAG冲突,若同时指定,则以CLIP_SAVE_FLAG为主 saveLayer()


从上面的表格中可以看到,ALL_SAVE_FLAG、MATRIX_SAVE_FLAG、CLIP_SAVE_FLAG是save()、saveLayer()共用的。而另外三个是saveLayer()专用的;我们一个个来解析下它们的不同之处 
在讲解之前,我们先考虑一下,如果让我们保存一个画布的状态,以便恢复,我们需要保存哪些内容呢? 
第一个是位置信息,第二个是大小信息;好像除此之外也没什么了。所以,位置信息对应的是MATRIX_SAVE_FLAG,大小信息对应的是:CLIP_SAVE_FLAG,这也就是save\saveLayer所共用的,而另外的三个函数,则是指定saveLayer新建的bitmap具有哪种特性。已经不再是保存画布的范畴了。

2、FLAG之MATRIX_SAVE_FLAG

(1)、save(int flag)与MATRIX_SAVE_FLAG 

我们知道canvas.translate(平移)、canvas.rotate(旋转)、canvas.scale(缩放)、canvas.skew(扭曲)其实都是利用位置矩阵matrix实现的,而MATRIX_SAVE_FLAG标识就是指定只保存这个位置矩阵,除此之外的其它任何内容都不会被保存; 
我们来举个例子来看下
public class MATRIX_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public MATRIX_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.save(Canvas.MATRIX_SAVE_FLAG);  
        canvas.rotate(40);  
        canvas.drawRect(100,0,200,100,mPaint);  
        canvas.restore();  
  
        mPaint.setColor(Color.YELLOW);  
        canvas.drawRect(100,0,200,100,mPaint);  
    }  
}  
我们直接看OnDraw函数,先调用 canvas.save(Canvas.MATRIX_SAVE_FLAG)将canvas的位置矩阵保存起来,然后将画布旋转40度之后,画一个绿色矩形; 
然后调用canvas.restore()之后将画布恢复,然后再在同一个位置画一个黄色的矩形。 
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

很明显,在canvas.restore()后,画布的旋转给恢复到了原来了状态。 
然后我们再来看看,如果我们给画布裁剪,看还能不能被恢复

public class MATRIX_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public MATRIX_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.save(Canvas.MATRIX_SAVE_FLAG);  
        canvas.clipRect(100,0,200,100);  
        canvas.drawColor(Color.GREEN);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

从效果图来看,我们恢复画布后,把画布全部染成了黄色,但并没有染全屏幕的画布,而只是clip后的一部分,这说明,被裁剪的画布没有被还原! 
前面我们说了调用 canvas.save(Canvas.MATRIX_SAVE_FLAG)只会保存了位置矩阵!恢复时,也
只会恢复画布的位置信息,有关画布的大小,是不会被恢复的! 

(2)、saveLayer()与MATRIX_SAVE_FLAG 

同样先来看旋转的例子:
public class MATRIX_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public MATRIX_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.drawColor(Color.RED);  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.MATRIX_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        canvas.rotate(40);  
        canvas.drawRect(100,0,200,100,mPaint);  
        canvas.restore();  
  
        mPaint.setColor(Color.YELLOW);  
        canvas.drawRect(100,0,200,100,mPaint);  
  
    }  
}      
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

这里在保存Flag时,多了一个Canvas.HAS_ALPHA_LAYER_SAVE_FLAG,表示在新建的画布在合成到上一个画布上时,直接覆盖,不清空所在区域原图像,这个标识这里先忽略,我们后面会具体讲。 
效果与原因都是与save()相同,指定保存Canvas.MATRIX_SAVE_FLAG,即canvas的位置信息,当调用canvas.restore()后,原始画布的旋转被恢复。所以再次画图到原始画布上时,是没有旋转的。 
我们还是直接来看例子吧,裁剪: 
下面我们举个例子来看看

public class MATRIX_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public MATRIX_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.drawColor(Color.GREEN);  
  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.MATRIX_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        canvas.clipRect(100,0,200,100);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

与上面的例子一样,在saveLayer中也只是保存Canvas.MATRIX_SAVE_FLAG,即canvas的位置信息,之后调用canvas.clipRect(100,0,200,100);将画板裁剪,注意我们在讲解canvas时提到了,无论哪个图层调用canvas的位置变换和裁剪操作,所有的画布都会受到连累,这些连累也只表现在画布以后的绘图上,之前画过的图像不会受到影响。 
所以在clipRect之前画出来的全屏绿色是不受影响的,当restore()以后,canvas只恢复了原始画布的位置信息而原始画布的大小却无法被恢复,所以当再调用 canvas.drawColor(Color.YELLOW),也只能画出来一小块了
注意:在上面的例子中用到了canvas.clipRect(),这个函数是不支持硬件加速的,原因参见《自定义控件三部曲之绘图篇(十)——Paint之setXfermode(一)》,所以需要添加setLayerType函数来禁用硬件加速。
所以MATRIX_SAVE_FLAG标识的结论来了:
1、当save\saveLayer调用Canvas.MATRIX_SAVE_FLAG标识时只会保存画布的位置矩阵信息,在canvas.restore()时也只会恢复位置信息,而改变过的画布大小是不会被恢复的。 
2、当使用canvas.saveLayer(Canvas.MATRIX_SAVE_FLAG)时,需要与Canvas.HAS_ALPHA_LAYER_SAVE_FLAG一起使用,不然新建画布所在区域原来的图像将被清空。(后面会讲原因)

3、FLAG之CLIP_SAVE_FLAG

这个标识的意思是仅保存Canvas的裁剪信息,而对于位置信息则不管不问,所以在canvas.restore()时,会只恢复Canvas的大小,而对于Canvas的旋转、平移等位置改变的信息是不会恢复的。 

(1)、save(int flag)与CLIP_SAVE_FLAG 

我们先来看个裁剪的例子:
public class CLIP_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public CLIP_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.drawColor(Color.RED);  
        canvas.save(Canvas.CLIP_SAVE_FLAG);  
        canvas.clipRect(100,0,200,100);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以看出在canvas.restore()后,canvas被恢复到初始化的全屏大小。 
然后我们再看一个旋转的例子

public class CLIP_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public CLIP_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        mPaint.setColor(Color.GREEN);  
        canvas.drawRect(100,0,200,100,mPaint);  
  
        canvas.save(Canvas.CLIP_SAVE_FLAG);  
        canvas.rotate(40);  
        canvas.restore();  
  
        mPaint.setColor(Color.YELLOW);  
        canvas.drawRect(100,0,200,100,mPaint);  
    }  
}      

效果图如下:

自定义控件之绘图篇:Canvas与图层(二)

我们先画了一个绿色的矩形,之后旋转画布,然后在调用 canvas.restore()恢复画布之后,再画上一个同样的黄色矩形。 
从效果图中可以看出,canvas在恢复时,并没有恢复旋转的画布,这也就是Canvas.CLIP_SAVE_FLAG的意义所在,只保存裁剪信息,不保存位置信息,所以恢复时,位置信息是不会被恢复的! 

(2)、saveLayer(int flag)与CLIP_SAVE_FLAG 

在添加上Canvas.HAS_ALPHA_LAYER_SAVE_FLAG标识以后,效果与canvas.save相同,这里就简单讲解一下。 
先看裁剪的例子:
public class CLIP_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public CLIP_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
           canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.CLIP_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        canvas.clipRect(100,0,200,100);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}    
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

效果与canvas.save一样,原因也很简单,因为Canvas.CLIP_SAVE_FLAG标识是可以恢复裁剪信息的。 
然后再来看看旋转。

public class CLIP_SAVE_FLAG_View extends View {  
    private Paint mPaint;  
    public CLIP_SAVE_FLAG_View(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(LAYER_TYPE_SOFTWARE,null);  
        mPaint = new Paint();  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        mPaint.setColor(Color.GREEN);  
        canvas.drawRect(100,0,200,100,mPaint);  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.CLIP_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        canvas.rotate(40);  
        canvas.restore();  
  
        mPaint.setColor(Color.YELLOW);  
        canvas.drawRect(100,0,200,100,mPaint);  
  
    }  
}      
效果与canvas.save相同
自定义控件之绘图篇:Canvas与图层(二)

因为Canvas.CLIP_SAVE_FLAG不能保存位置信息,所以在canvas.restore()后,旋转并没有被恢复。 
所以CLIP_SAVE_FLAG标识的结论来了:
1、当save/saveLayer调用 Canvas.CLIP_SAVE_FLAG时只会保存画布的裁剪信息,在canvas.restore()时也只会恢复裁剪信息,而改变过的画布位置信息是不会被恢复的。 
2、当使用canvas.saveLayer(Canvas.CLIP_SAVE_FLAG)时,需要与Canvas.HAS_ALPHA_LAYER_SAVE_FLAG一起使用,不然新建画布所在区域原来的图像将被清空。

4、FLAG之HAS_ALPHA_LAYER_SAVE_FLAG和FULL_COLOR_LAYER_SAVE_FLAG

这两个标识都是saveLayer()专用的 
HAS_ALPHA_LAYER_SAVE_FLAG表示新建的bitmap画布在与上一个画布合成时,不会将上一层画布内容清空,直接盖在上一个画布内容上面。 
FULL_COLOR_LAYER_SAVE_FLAG则表示新建的bimap画布在与上一个画布合成时,先将上一层画布对应区域清空,然后再盖在上面。 
下面我们分别举例子来看 
注意一定要在view中禁用掉硬件加速,因为在api 21之后,才支持saveLayer 

(1)、FULL_COLOR_LAYER_SAVE_FLAG

public class ALPHA_COLOR_FALG_VIEW extends View {  
    private Paint mPaint;  
    public ALPHA_COLOR_FALG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
  
        canvas.saveLayer(0,0,500,500,mPaint,Canvas.FULL_COLOR_LAYER_SAVE_FLAG);  
        canvas.drawRect(100,100,300,300,mPaint);  
        canvas.restore();  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

我们在saveLayer时,新建bitmap画布的大小为(0,0,500,500),然后在新建画布中画了一个矩形(100,100,300,300),由于我们使用的标识是Canvas.FULL_COLOR_LAYER_SAVE_FLAG,所以新建画布在与上一层画布合成时,会先把上一层画布对应区域的图像清空掉,然后再盖上新建画布。由于新建画布中除了绿色矩形,其它位置都是透明像素,所以就显示出Activity的底色(黑色)。如果你把activity的背景色在xml中设置为白色,做出来的效果图中,露出来的就是白色了: 
main.xml

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
              android:orientation="vertical"  
              android:layout_width="fill_parent"  
              android:layout_height="fill_parent"  
              android:background="#ffffff"  
        >  
        …………  

对应的效果为:

自定义控件之绘图篇:Canvas与图层(二)

(2)、HAS_ALPHA_LAYER_SAVE_FLAG 

我把简单把上面的示例代码改一下,把Canvas.FULL_COLOR_LAYER_SAVE_FLAG改成Canvas.HAS_ALPHA_LAYER_SAVE_FLAG:
public class ALPHA_COLOR_FALG_VIEW extends View {  
    private Paint mPaint;  
    public ALPHA_COLOR_FALG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
  
        canvas.saveLayer(0,0,500,500,mPaint,Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        canvas.drawRect(100,100,300,300,mPaint);  
        canvas.restore();  
    }  
}  
效果图为:
自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以看出,saveLayer新建的画布在与上一层画布合成时,并没有把上一层画布对应区域清空,而是直接盖在上面。 

(3)、共用时,以HAS_ALPHA_LAYER_SAVE_FLAG为主 

很明显这两个标识是相互冲突的,因为Canvas.HAS_ALPHA_LAYER_SAVE_FLAG表示直接盖上去而不清空上一画布的图像,而Canvas.FULL_COLOR_LAYER_SAVE_FLAG则表示先将上一画布对应区域图像清空,然后再盖上去。当他们共用时,以哪个标识位为主呢? 
我们来做个试验:
public class ALPHA_COLOR_FALG_VIEW extends View {  
    private Paint mPaint;  
    public ALPHA_COLOR_FALG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
        canvas.saveLayer(0,0,500,500,mPaint,Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG);  
        canvas.drawRect(100,100,300,300,mPaint);  
        canvas.restore();  
    }  
}  
还是上面的例子,我们同时加入两个标识,看以哪个结果显示,效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

所以从效果图中也可以看出,当这两个标识同时使用时,以Canvas.HAS_ALPHA_LAYER_SAVE_FLAG为主; 

(4)、当saveLayer只指定MATRIX_SAVE_FLAG/CLIP_SAVE_FLAG的合成方式 

前面我们在讲解saveLayer的MATRIX_SAVE_FLAG、CLIP_SAVE_FLAG标识时,都强制加上了Canvas.HAS_ALPHA_LAYER_SAVE_FLAG标识,意思是让其在合成时不清空上一画布图像。那么问题来了,当我们只指定MATRIX_SAVE_FLAG、CLIP_SAVE_FLAG标识时,Android默认的合成方式哪一个呢? 
下面我们举个例子来看下:
public class ALPHA_COLOR_FALG_VIEW extends View {  
    private Paint mPaint;  
  
    public ALPHA_COLOR_FALG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
  
        canvas.saveLayer(0,0,500,500,mPaint,Canvas.MATRIX_SAVE_FLAG);  
        canvas.rotate(40);  
        canvas.drawRect(100, 100, 300, 300, mPaint);  
        canvas.restore();  
    }  
}      
效果图如下:

自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以看出,在默认情况下使用的是Canvas.FULL_COLOR_LAYER_SAVE_FLAG标识,即先清空上一层画布对应区域的图像,然后再合成,所以这也是我们在上面的例子中强制添加HAS_ALPHA_LAYER_SAVE_FLAG标识的原因
所以有关这两个标识的结论来了:
1、HAS_ALPHA_LAYER_SAVE_FLAG表示新建的bitmap画布在与上一个画布合成时,不会将上一层画布内容清空,直接盖在上一个画布内容上面。 
2、FULL_COLOR_LAYER_SAVE_FLAG则表示新建的bimap画布在与上一个画布合成时,先将上一层画布对应区域清空,然后再盖在上面。 
3、当HAS_ALPHA_LAYER_SAVE_FLAG与FULL_COLOR_LAYER_SAVE_FLAG两个标识同时指定时,以HAS_ALPHA_LAYER_SAVE_FLAG为主 
4、当即没有指定HAS_ALPHA_LAYER_SAVE_FLAG也没有指定FULL_COLOR_LAYER_SAVE_FLAG时,系统默认使用FULL_COLOR_LAYER_SAVE_FLAG;

5、FLAG之CLIP_TO_LAYER_SAVE_FLAG

(1)、概述 

这个标识比较犯贱,它的意义是,在新建bitmap前,先把canvas给裁剪,前面我们讲过canvas代表的是画板的意思,一旦画板被裁剪,那么其中的各个画布都会被受到影响。而且由于它是在新建bitmap前做的裁剪,所以是无法恢复的!
public class CLIP_TO_LAYER_SAVE_FLAG_VIEW extends View {  
    private Paint mPaint;  
    public CLIP_TO_LAYER_SAVE_FLAG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
        canvas.saveLayer(0, 0, 500, 500, mPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以看出,当我们调用canvas.saveLayer(0, 0, 500, 500, mPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG)时,canvas画板就被裁剪了,不仅影响了自己,而且还把view的原始画布给影响了,虽然在调用了canvas.restore(),但最后一句在将原始画布填充为黄色,也可以看出,原始画布没有被恢复! 

(2)、与CLIP_SAVE_FLAG共用时,Canvas将被恢复 

我们知道,前面有一个保存裁剪信息的标识:CLIP_SAVE_FLAG,假如我们让它裁剪时,先保存裁剪区域,是不是可以恢复过来呢?
public class CLIP_TO_LAYER_SAVE_FLAG_VIEW extends View {  
    private Paint mPaint;  
    public CLIP_TO_LAYER_SAVE_FLAG_VIEW(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.GREEN);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        canvas.drawColor(Color.RED);  
        canvas.saveLayer(0, 0, 500, 500, mPaint, Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);  
        canvas.restore();  
  
        canvas.drawColor(Color.YELLOW);  
    }  
}  
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以看出canvas被恢复了,不过canvas被恢复也,也就失去了Canvas.CLIP_TO_LAYER_SAVE_FLAG标识的意义了。 
所以这个CLIP_TO_LAYER_SAVE_FLAG标识的结论来了:
1、CLIP_TO_LAYER_SAVE_FLAG意义是在新建bitmap前,先把canvas给裁剪,一旦画板被裁剪,那么其中的各个画布都会被受到影响。而且由于它是在新建bitmap前做的裁剪,所以是无法恢复的; 
2、当CLIP_TO_LAYER_SAVE_FLAG与CLIP_SAVE_FLAG标识共用时,在调用restore()后,画布将被恢复

6、FLAG之ALL_SAVE_FLAG

这个标识是我们最常用的,它是所有标识的公共集合。 
对于save(int flag)来讲,ALL_SAVE_FLAG = MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG;即保存位置信息和裁剪信息 
对于save(int flag)来讲,ALL_SAVE_FLAG = MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG很容易理解,因为save(int flag)函数只能使用MATRIX_SAVE_FLAG 、CLIP_SAVE_FLAG这两个标识。 
对于saveLayer(int flag)来讲,ALL_SAVE_FLAG = MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG|HAS_ALPHA_LAYER_SAVE_FLAG;即保存保存位置信息和裁剪信息,新建画布在与上一层画布合成时,不清空原画布内容。
原本来讲saveLayer的ALL_SAVE_FLAG标识应当是它所能使用的所有标识的集合,即应当是ALL_SAVE_FLAG = MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG|HAS_ALPHA_LAYER_SAVE_FLAG|FULL_COLOR_LAYER_SAVE_FLAG|CLIP_TO_LAYER_SAVE_FLAG,但由于HAS_ALPHA_LAYER_SAVE_FLAG与FULL_COLOR_LAYER_SAVE_FLAG共用时以HAS_ALPHA_LAYER_SAVE_FLAG为主,CLIP_TO_LAYER_SAVE_FLAG与CLIP_SAVE_FLAG共用时,CLIP_TO_LAYER_SAVE_FLAG将无效,所以最终ALL_SAVE_FLAG = MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG|HAS_ALPHA_LAYER_SAVE_FLAG; 
在理解了上面各个TAG的样式以后,这个TAG的难度几乎没有,这里就不再举例了。

四、restore()与restoreToCount()

1、restore()

restore()的作用就是把回退栈中的最上层画布状态出栈,恢复画布状态。在《自定义控件之绘图篇(四):canvas变换与操作》中已经详细地说明了restore()函数的用法。这里就不再细讲了

2、restoreToCount(int count)

先看下这几个save系列函数的声明
public int save()  
public int save(int saveFlags)  
public int saveLayer(RectF bounds, Paint paint, int saveFlags)  
public int saveLayer(float left, float top, float right, float bottom,Paint paint, int saveFlags)  
public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)  
public int saveLayerAlpha(float left, float top, float right, float bottom,int alpha, int saveFlags)  
在save()、saveLayer()、saveLayerAlpha()保存画布后,都会返回一个ID值,这个ID值表示当前保存的画布信息的栈层索引(从0开始),比如保存在第三层,则返回2; 
而restoreToCount的声明如下:
public void restoreToCount(int saveCount);  
它表示一直退栈,一直退到指定count的层数为栈顶为止;注意这个saveCount起始值是从1开始的,也就是说它比对应栈的索引要多1; 
比如,我们开始的栈已经有两层,然后我们调用如下代码:
int id = canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.ALL_SAVE_FLAG);  
canvas.restoreToCount(id);  
调用canvas.saveLayer后,新保存的画布放在了第三层,返回的id的值是对应的索引即2 
而canvas.restoreToCount(id);则表示一直退栈,把栈一直退到第二层在栈顶的位置,刚好把新建的第三层给退出掉。 
所以利用这个特性,我们可以调用save函数的时候,把对应的id保存住,然后canvas.restoreToCount(id)就可以把栈的状态回退到生成这个id前的状态。 
下面我们举个例子来看下:
public class RestoreToCountView extends View {  
    private Paint mPaint;  
    private String TAG = "qijian";  
    public RestoreToCountView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
  
        mPaint = new Paint();  
        mPaint.setColor(Color.RED);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        int id1 = canvas.save();  
        canvas.clipRect(0,0,800,800);  
        canvas.drawColor(Color.RED);  
        Log.d(TAG,"count:"+canvas.getSaveCount()+"  id1:"+id1);  
  
        int id2 = canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.ALL_SAVE_FLAG);  
        canvas.clipRect(100,100,700,700);  
        canvas.drawColor(Color.GREEN);  
        Log.d(TAG,"count:"+canvas.getSaveCount()+"  id2:"+id2);  
  
        int id3 = canvas.saveLayerAlpha(0,0,getWidth(),getHeight(),0xf0,Canvas.ALL_SAVE_FLAG);  
        canvas.clipRect(200,200,600,600);  
        canvas.drawColor(Color.YELLOW);  
        Log.d(TAG,"count:"+canvas.getSaveCount()+"  id3:"+id3);  
  
        int id4 = canvas.save(Canvas.ALL_SAVE_FLAG);  
        canvas.clipRect(300,300,500,500);  
        canvas.drawColor(Color.BLUE);  
        Log.d(TAG,"count:"+canvas.getSaveCount()+"  id4:"+id4);  
    }  
}  
在onDraw函数中,我们连续对canvas做裁剪,并且在裁剪后,把当前画布画上一层不同的颜色,然后把当前的栈的层数和最高层的索引打出来 
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)


Log日志如下:

自定义控件之绘图篇:Canvas与图层(二)

然后我们更改一下上面的代码:

protected void onDraw(Canvas canvas) {  
   super.onDraw(canvas);  
  
   int id1 = canvas.save();  
   canvas.clipRect(0,0,800,800);  
   canvas.drawColor(Color.RED);  
   Log.d(TAG,"count:"+canvas.getSaveCount()+"  id1:"+id1);  
  
   int id2 = canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.ALL_SAVE_FLAG);  
   canvas.clipRect(100,100,700,700);  
   canvas.drawColor(Color.GREEN);  
   Log.d(TAG,"count:"+canvas.getSaveCount()+"  id2:"+id2);  
  
   int id3 = canvas.saveLayerAlpha(0,0,getWidth(),getHeight(),0xf0,Canvas.ALL_SAVE_FLAG);  
   canvas.clipRect(200,200,600,600);  
   canvas.drawColor(Color.YELLOW);  
   Log.d(TAG,"count:"+canvas.getSaveCount()+"  id3:"+id3);  
  
   int id4 = canvas.save(Canvas.ALL_SAVE_FLAG);  
   canvas.clipRect(300,300,500,500);  
   canvas.drawColor(Color.BLUE);  
   Log.d(TAG,"count:"+canvas.getSaveCount()+"  id4:"+id4);  
  
   canvas.restoreToCount(id3);  
   canvas.drawColor(Color.GRAY);  
   Log.d(TAG,"count:"+canvas.getSaveCount());  
}  
我们在最后添加上canvas.restoreToCount(id3);,然后把画布整个绘成灰色。 
效果图如下:
自定义控件之绘图篇:Canvas与图层(二)

Log日志如下:

自定义控件之绘图篇:Canvas与图层(二)

从代码中可以看出调用canvas.restoreToCount(id3)后,将恢复到生成id3之前的画布状态,id3之前的画布状态就是(100,100,700,700)

3、restore()与restoreToCount(int count)关系

它们两个针对的都是同一个栈,所以是完全可以通用的,不同的是restore()是默认将栈顶内容退出还原画布,而restoreToCount(int count)则是一直退栈,直到指定层count做为栈顶,将此之前的所有动作都恢复。 
大家可能还有个疑问,前面我们讲了各种FLAG,在应用不同FLAG时,都是保存在同一个栈中吗,我们下测试一下
public class RestoreToCountView extends View {  
    private Paint mPaint;  
    private String TAG = "qijian";  
    public RestoreToCountView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
        mPaint = new Paint();  
        mPaint.setColor(Color.RED);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        canvas.save();  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
        canvas.save(Canvas.ALL_SAVE_FLAG);  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.CLIP_SAVE_FLAG);  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.MATRIX_SAVE_FLAG);  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
        canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.ALL_SAVE_FLAG);  
        Log.d(TAG,"count:"+canvas.getSaveCount());  
    }  
}      

在这个例子中,我们多次调用不同的save函数和不同的FLAG,然后将栈中层数打出来,日志如下:
自定义控件之绘图篇:Canvas与图层(二)

从效果图中可以明显看出,每save一次,栈的层数都在加一,所以无论哪种save方法,哪个FLAG标识,保存画布时都使用的是同一个栈
所以restore()与restoreToCount(int count)的结论来了:
1、restore的意义是把回退栈中的最上层画布状态出栈,恢复画布状态。restoreToCount(int count)的意义是一直退栈,直到指定层count做为栈顶,将此之前的所有动作都恢复。 
2、所以无论哪种save方法,哪个FLAG标识,保存画布时都使用的是同一个栈 
3、restore()与restoreToCount(int count)针对的都是同一个栈,所以是完全可以通用和混用的。

好了,有关保存图层的知识到这里就结束了,这两篇内容理解起来可能会比较困难,多看两遍喽,有关FLAG标识的知识,如果看不懂就算了,会用ALL_SAVE_FLAG就行,其它标识用到的机会比较少。如果有机会开视频教程的话,再给大家具体演示一下,应该会比较好理解,暂且期待下吧。

如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9510189 (与第二篇对应资源合并在一个工程中)

首发:http://blog.csdn.net/harvic880925/article/details/51332494



FLAG 意义 适用范围
ALL_SAVE_FLAG 保存所有的标识 save()、saveLayer()
MATRIX_SAVE_FLAG 仅保存canvas的matrix数组 save()、saveLayer()
CLIP_SAVE_FLAG 仅保存canvas的当前大小 save()、saveLayer()
HAS_ALPHA_LAYER_SAVE_FLAG 标识新建的bmp具有透明度,在与上层画布结合时,透明位置显示上图图像,与FULL_COLOR_LAYER_SAVE_FLAG冲突,若同时指定,则以HAS_ALPHA_LAYER_SAVE_FLAG为主 saveLayer()
FULL_COLOR_LAYER_SAVE_FLAG 标识新建的bmp颜色完全独立,在与上层画布结合时,先清空上层画布再覆盖上去 saveLayer()
CLIP_TO_LAYER_SAVE_FLAG 在保存图层前先把当前画布根据bounds裁剪,与CLIP_SAVE_FLAG冲突,若同时指定,则以CLIP_SAVE_FLAG为主 saveLayer()

FLAG意义适用范围ALL_SAVE_FLAG保存所有的标识save()、saveLayer()MATRIX_SAVE_FLAG仅保存canvas的matrix数组save()、saveLayer()CLIP_SAVE_FLAG仅保存canvas的当前大小save()、saveLayer()HAS_ALPHA_LAYER_SAVE_FLAG标识新建的bmp具有透明度,在与上层画布结合时,透明位置显示上图图像,与FULL_COLOR_LAYER_SAVE_FLAG冲突,若同时指定,则以HAS_ALPHA_LAYER_SAVE_FLAG为主saveLayer()FULL_COLOR_LAYER_SAVE_FLAG标识新建的bmp颜色完全独立,在与上层画布结合时,先清空上层画布再覆盖上去saveLayer()CLIP_TO_LAYER_SAVE_FLAG在保存图层前先把当前画布根据bounds裁剪,与CLIP_SAVE_FLAG冲突,若同时指定,则以CLIP_SAVE_FLAG为主saveLayer()