android view的繪製流程

當一個應用啓動的時候,會啓動一個主activity,android系統會根據activity的佈局來對它進行繪製。每個view負責繪製自己,而viewgroup還需要負責通知自己的子view進行繪製操作。視圖繪製的過程可以分爲三個步驟,分別是 Measure LayoutDraw

private void performTraversals() {
        ......
        //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
        //lp.width和lp.height在創建ViewGroup實例時等於MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

Measure

Measure操作用來計算View的實際大小,對於viewGroup來說,由viewGroup在它的measureChild方法中傳遞給子view,viewGroup通過遍歷自身所有的子view,並逐個調用子view的measure方法完成測量工作。當測量某個指定的view的時候,根據父容器的MeasureSpec和子view的LayoutParams等信息來計算子view的MeasureSpec

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {    
   final View child = getChildAt(i);    
   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍歷自己的子View,只要不是GONE的都會參與測量,
     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
     ...
     ....
   }
}
//測量某個指定的view
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最後都會封裝到這個個LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

//根據父容器的MeasureSpec和子view的LayoutParams等信息來計算子view的MeasureSpec

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);    

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  

//通過父View的MeasureSpec和子View的自己LayoutParams的計算,算出子View的MeasureSpec,然後父容器傳遞給子容器的
// 然後讓子View用這個MeasureSpec(一個測量要求,比如不能超過多大)去測量自己,如果子View是ViewGroup 那還會遞歸往下測量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

對於每個View的measure方法,最終的測量是通過回調onMeasure方法實現的。這個通常由view的特定子類自己實現,卡發着也可以通過重寫這個方法實現自定義view。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  .....
}
//如果需要自定義測量過程,則子類可以重寫這個方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  setMeasuredDimension(
  getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            
  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//如果view沒有重寫onMeasure方法,則默認會調用getDefaultSize來獲得view的寬高
protected int getSuggestedMinimumWidth() { 
   return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 
} 
//@param size參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值  
public static int getDefaultSize(int size, int measureSpec) {    
   int result = size;    
   int specMode = MeasureSpec.getMode(measureSpec);    
   int specSize = MeasureSpec.getSize(measureSpec);    
   switch (specMode) {    
   case MeasureSpec.UNSPECIFIED:        //表示該View的大小父視圖未定,設置爲默認值 
     result = size;  
     break;    
   case MeasureSpec.AT_MOST:    
   case MeasureSpec.EXACTLY:        
     result = specSize;  
     break;   
 }    
return result;
}

主要要三種測量模式,

  • UNSPECIFIED :不指定測量模式,俯視圖沒有限制子視圖餓大小,子視圖可以使想要的任何尺寸,通常用於系統內部,應用開發很少用到

  • EXACTLY 精確測量模式,當該視圖的layout_width 或者layout_height 指定爲具體數值或者match_parent時生效,表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量值就是SpecSize的值

  • AT_MOST 最大值模式,當該視圖的layout_width或者layout_height指定爲wrap_content時生效,此時,子視圖的尺寸可以使不超過父視圖允許的最大尺寸的任何尺寸。

Layout

Layout 過程用來確定View在父容器中的佈局位置,它是由父容器獲取子View的位置參數後,調用子View的layout方法並將位置參數傳入實現的,

Draw

Draw用來將控件繪製出來

public void draw(Canvas canvas) {
    ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, 如果需要的話,繪製view背景
    ...
        background.draw(canvas);
    ...
        // skip step 2 & 5 if possible (common case) 一般情況會跳過第二步和第五步
    ...
        // Step 2, save the canvas layers  如果需要的話,保存canvas圖層,爲fading做準備
        ...

        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
    ...
        // Step 3, draw the content 繪製view的內容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children 繪製view的子view
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers 如果需要的話,繪製View的fading邊緣來恢復圖層

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }
    ...
        // Step 6, draw decorations (scrollbars) 繪製View的裝飾例如滾動條()
        onDrawScrollBars(canvas);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章