1. 程式人生 > >View和ViewGroup的基本繪製流程

View和ViewGroup的基本繪製流程

需要了解的

  • 先來張圖說明一下它們的關係
    view和viewgroup
    你還要知道ViewGroup之間是可以巢狀的.

View的繪製流程

  • 不知道大家有沒有這種疑惑, 為什麼我們在寫佈局檔案的時候, 一定要寫layout_width和layout_height呢, 難道就沒有預設值嗎? 顏色, 背景, 等等其他的都有預設值, 為什麼寬高就一定要我們手動寫呢? 接下來就讓我們一起來解答這個疑惑吧.
  • 繪製流程的原始碼就不貼出來了, 有興趣的可以開啟View的原始碼對照著來看, 印象會更深刻, 當然, 不看原始碼, 理解以下的例項程式碼, 也不會影響你對整個流程的理解.
  • 首先看一下View的繪製流程示例:
public
class MyView extends View { private static final String TAG = "MyView"; public MyView(Context context, AttributeSet attrs) { super(context, attrs); } /** * measure - > onMeasure ,view的原始碼中,measure會呼叫onMeasure */ @Override protected void onMeasure(int widthMeasureSpec, int
heightMeasureSpec) { //測量, 表示這個view的大小, 在View的原始碼中, Measure是final修飾的, 我們只能重寫onMeasure方法 // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.d(TAG, "measure"); } /** * layout - > setFrame 和 onLayout */ @Override public
void layout(int l, int t, int r, int b) { //佈局,決定了擺放在父容器中的哪個位置 // TODO Auto-generated method stub super.layout(l, t, r, b); Log.d(TAG, "layout"); } /** * draw - > onDraw */ @Override public void draw(Canvas canvas) { //繪製 // TODO Auto-generated method stub super.draw(canvas); Log.d(TAG, "draw"); } }
  • 以上就是View繪製顯示在螢幕上必定會呼叫的三個方法, 可能你還不太理解, 不過沒關係, 先混個臉熟, 有個印象先, 下面我們一個個分析.

View的onMeasure

  • 來看一段Demo
public class MyView extends View {

    private static final String TAG = "MyView";

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * measure -> onMeasure
     * 
     * 1.父容器拿到孩子的申請的寬高layout_width, layout_height封裝成寬高的期望widthMeasureSpec和heightMeasureSpec
     * 父容器Relativelayout(或者其他Linearlyout)
     * 呼叫MyView的 measure(int widthMeasureSpec, int heightMeasureSpec)傳入對孩子寬高的期望
     * measure -> onMeasure(widthMeasureSpec, heightMeasureSpec)
     * 
     * @param widthMeasureSpec 父容器(RelativeLaoyut)對孩子MyView的寬度期望, 跟layout_width相關
     * @param heightMeasureSpec 父容器(RelativeLaoyut)對孩子MyView的高度期望, 跟layout_height相關
     * 這是我們為什麼一定要指定layout_width和layout_height的原因.
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
        * int widthMeasureSpec
        * 32位二進位制
        * 前兩位 是測量模式 mode
        * public static final int UNSPECIFIED = 0 << MODE_SHIFT; 父容器對孩子沒有任何的限制,孩子想多大多大
        * public static final int EXACTLY     = 1 << MODE_SHIFT; 父容器對孩子有確切的大小要求,大小就會後30位
        * public static final int AT_MOST     = 2 << MODE_SHIFT; 父容器對孩子的最大值有要求,大小就會後30位
        * 後30位表示大小
        */
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        Log.d(TAG, "onMeasure mode " + (mode>>30) + " " + size);

        //super方法預設使用父容器對我的期望的寬高
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //我們也可以不呼叫super直接呼叫setMeasuredDimension(50, 50)來指定寬高
    }

}
  • 註釋裡的setMeasuredDimension(int measuredWidth, int measuredHeight)是一個重要的方法, 它是整個測量結束的標誌, 只有這個方法呼叫了, 我們才能呼叫getMeasuredWidth()或者getMeasuredHeight()方法獲得測量寬高(注意, 不是實際寬高, 實際寬高要在佈局完成之後)。
  • 自定義View如果要使用wrap_content屬性的話,則需重寫onMeasure方法。

View的layout

  1. layout方法裡面呼叫setFrame(),給View的上下左右四個位置mLeft, mTop,mRight, mBottom賦值,完成佈局工作.
  2. onLayout()是一個空的方法,說明具體的佈局不應該由view來決定
  3. 我們的view並不需要關心layout方法, 佈局的事應該交由父容器去處理, 讓它決定它的孩子應該擺放在哪個地方.
  4. 在佈局完成後, 我們才能呼叫getWidth()和getHeight獲得實際的寬高.
  5. getWidth()和getMeasuredWidth()的區別: getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設定的,而getWidth()方法中的值則是通過檢視右邊的座標減去左邊的座標計算出來的。

View的draw

  • view的繪製分為6步:

    1. 對檢視的背景進行繪製
    2. If necessary, save the canvas’ layers to prepare for fading (暫時忽略它)
    3. 對檢視的內容進行繪製, 在onDraw(canvas)方法中完成
    4. 對當前檢視的所有子檢視進行繪製 ,呼叫dispatchDraw。
    5. If necessary, draw the fading edges and restore layers (暫時忽略它)
    6. 繪製裝飾品(如滾動條)任何一個檢視都是有滾動條的,只是一般情況下我們都沒有讓它顯示出來而已.
  • 即我們關心四個步驟:

    1. 繪製背景
    2. 繪製內容
    3. 繪製孩子
    4. 繪製裝飾
  • 繪製需要兩個類, 畫布(Canvas)和畫筆(Paint), 通過以下Demo通過onDraw方法利用畫筆在畫布上繪製我們的圖案吧.

public class MyView extends View {

    private Paint mPaint;
    private Bitmap mBitmap;
    private Path mPath;
    private RectF mOval;


    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        //設定去鋸齒
        mPaint.setAntiAlias(true);
        //配置畫筆,畫空心圓
        mPaint.setStyle(Style.STROKE);
        //設定畫筆寬度
        mPaint.setStrokeWidth(3);

        //設定畫筆顏色
        mPaint.setColor(Color.BLUE);

        //畫圖片時需要設定圖片
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);

        //設定扇形的大小
        mOval = new RectF(5, 5, 195, 195);

        initPath();
    }

    private void initPath() {
        mPath = new Path();
        //確定三個點
        int x1 = 100, y1 = 5;
        int x2 = 195, y2 = 195;
        int x3 = 5, y3 = 195;
        //移動到第一個點
        mPath.moveTo(x1, y1);
        //連結第一個點和第二個點
        mPath.lineTo(x2, y2);
        //連結第二個點和第三個點
        mPath.lineTo(x3, y3);
        mPath.lineTo(x1, y1);

    }

    /**
     * 不要在onDraw方法裡面建立新的物件,因為onDraw方法可能會頻繁呼叫
     */
    @Override
    protected void onDraw(Canvas canvas) {
//      6. 裁剪
//      canvas.clipPath(mPath);


//      1. 畫直線
//      int startX =5, startY = 100;
//      int stopX = 195, stopY = 100;
//      canvas.drawLine(startX, startY, stopX, stopY, mPaint);
//      2. 畫圓
//      int cx = 100,  cy = 100;
//      int radius = 80;
//      canvas.drawCircle(cx, cy, radius, mPaint);
//      3. 畫空心圓

//      4. 畫圖片
//      canvas.drawBitmap(mBitmap, 0, 0, mPaint);
//      5. 畫三角形
//      canvas.drawPath(mPath, mPaint);

        //7.畫扇形
        int startAngle = -90; //開始的角度
        int sweepAngle = 45;  //掃過的角度
        boolean useCenter = false;//是否畫出扇形的兩邊
        canvas.drawArc(mOval, startAngle, sweepAngle, useCenter, mPaint);
    }
}

View的重新繪製

  • invalidate(); //觸發View的重新繪製 onDraw
  • postInvalidate(); //請求在主執行緒重新繪製控制元件 onDraw

ViewGroup的繪製流程

  • ViewGroup繼承View,繪製流程跟View是一致

ViewGroup的測量

  • 相同點:measure -> onMeasure
  • 不同點:ViewGroup需要在onMeasure去測量孩子
  • 自定義ViewGroup一定要重寫onMeasure方法,如果不重寫則子View獲取不到寬和高。重寫是在onMeasure方法中呼叫measureChildern()方法,遍歷出所有子View並對其進行測量。

ViewGroup的佈局

  • 相同點:layout (父容器呼叫) -》 onLayout
  • 不同點:ViewGroup需要實現onLayout方法去佈局孩子,呼叫孩子的layout方法,指定孩子上下左右的位置
  • requestLayout();//請求重新佈局 onLayout

ViewGroup的繪製

  • 相同點:draw -> onDraw
  • 不同點:ViewGroup一般不繪製自己,ViewGroup預設實現dispatchDraw去繪製孩子