Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義控件三部曲之繪圖篇(十四)——Canvas與圖層(二)

自定義控件三部曲之繪圖篇(十四)——Canvas與圖層(二)

編輯:關於Android編程

一、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.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);
    }
}
效果圖如下:
\

 

從效果圖來看,我們恢復畫布後,把畫布全部染成了黃色,但並沒有染全屏幕的畫布,而只是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);

    }
}    
效果圖如下:
\

 

這裡在保存Flag時,多了一個Canvas.HAS_ALPHA_LAYER_SAVE_FLAG,表示在新建的畫布在合成到上一個畫布上時,直接覆蓋,不清空所在區域原圖像,這個標識這裡先忽略,我們後面會具體講。
效果與原因都是與save()相同,指定保存Canvas.MATRIX_SAVE_FLAG,即canvas的位置信息,當調用canvas.revert()後,原始畫布的旋轉被恢復。所以再次畫圖到原始畫布上時,是沒有旋轉的。
我們還是直接來看例子吧,裁剪:
下面我們舉個例子來看看

 

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);
    }
}
效果圖如下:
\

 

與上面的例子一樣,在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.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.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.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.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();
    }
}
效果圖如下:
\

 

我們在saveLayer時,新建bitmap畫布的大小為(0,0,500,500),然後在新建畫布中畫了一個矩形(100,100,300,300),由於我們使用的標識是Canvas.FULL_COLOR_LAYER_SAVE_FLAG,所以新建畫布在與上一層畫布合成時,會先把上一層畫布對應區域的圖像清空掉,然後再蓋上新建畫布。由於新建畫布中除了綠色矩形,其它位置都是透明像素,所以就顯示出Activity的底色(黑色)。如果你把activity的背景色在xml中設置為白色,做出來的效果圖中,露出來的就是白色了:
main.xml

 



        …………
對應的效果為:
\

 

 

(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();
    }
}
效果圖為:
\

 

從效果圖中可以看出,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.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.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.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.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()的作用就是把回退棧中的最上層畫布狀態出棧,恢復畫布狀態。這裡就不再細講了

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做裁剪,並且在裁剪後,把當前畫布畫上一層不同的顏色,然後把當前的棧的層數和最高層的索引打出來
效果圖如下:
\

 

Log日志如下:

\

然後我們更改一下上面的代碼:

 

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);,然後把畫布整個繪成灰色。
效果圖如下:
\

 

Log日志如下:

\

從代碼中可以看出調用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,然後將棧中層數打出來,日志如下:
\

 

從效果圖中可以明顯看出,每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就行,其它標識用到的機會比較少。如果有機會開視頻教程的話,再給大家具體演示一下,應該會比較好理解,暫且期待下吧。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved