天天看點

Android View 繪制過程

Android View 的繪制 的基本過程是: 1. Measure 測量, 即确定View 的大小 2. Layout 布局,即确定View 的擺放位置 3. Draw, 畫View

首先看Measure  關于Measure 放方法有:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)      final方法 , 不能重寫 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)      可以重寫, 若想影響測量 課在次方法中做處理 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)       最終決定 View 大小的方法, 該不放不能重寫, 且必須在 onMeasure 調用

在ViewGroup 中還有一些 關于Measure的方法: protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)      循環周遊 調用 measureChild 方法 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)      在這個方法中 會調用子View 的 measure 方法去測量子類

ok 上面說了一些 Measure的相關方法, 用到最多的一班還隻是 onMeasure 方法,自己改變View 的測量的大小 需要有一點需要注意的是 在onMeasure 中必須要調用 setMeasuredDimension 方法不然會報錯

下面來看看 View 預設的onMeasure 方法: 

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
           

隻是的簡單的調用了 setMeasuredDimension 方法,  我在onMeasure 方法中需要計算好大小, 再調用 setMeasuredDimension 方法 我們看到上面  哪些關于 Measure方法參數 都是int 行, 但是他們 不是一個 簡單的 寬度高低, 還有有些其他的東西再裡面: 例如: widthMeasureSpec 這裡有兩個資料,  前3位代表, 測量模式,  後面資料代表寬度: 測量模式有: 

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;// 移動的位數
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;// mask 0011 向右移動30位

        public static final int UNSPECIFIED = 0 << MODE_SHIFT;// 0000  向右移動30位
        public static final int EXACTLY     = 1 << MODE_SHIFT;// 0001向右移動30位
        public static final int AT_MOST     = 2 << MODE_SHIFT;// 0010向右移動30位
        // 向要知道 是什麼模式可以調用:
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
       // 擷取具體尺寸
       public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

     // 自己 做一個這個int 資料, 傳入 尺寸 和 模式就好了
     public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
      }
}
           

具體的繪制過程隻能很籠統的說一下: 在RootView 中, 會去調用 ViewGroup的measure方法: 然後在View Group  在onMeasure 中 又會依次調用子View 的measure 方法      ViewGroup周遊完後, 也會計算處自己的大小最後調用 setMeasuredDimension 方法 最後子View 調用 onMeasure setMeasuredDimension 方法

View 的大小 是子View 和父View共同決定, 我們在xml 中給出的width height 知識期望值, 在别的部落格上看到一張表表示的還不錯:  :

父視圖能力尺寸 子視圖期望尺寸 子視圖最終允許尺寸
EXACTLY + Size1 EXACTLY + Size2 EXACTLY + Size2
EXACTLY + Size1 fill_parent/match_parent EXACTLY +Size1
EXACTLY + Size1 wrap_content AT_MOST +Size1
AT_MOST +Size1 EXACTLY + Size2 EXACTLY +Size2
AT_MOST +Size1 fill_parent/match_parent AT_MOST +Size1
AT_MOST +Size1 wrap_content AT_MOST +Size1
UNSPECIFIED+Size1 EXACTLY + Size2 EXACTLY + Size2
UNSPECIFIED+Size1 fill_parent/match_parent UNSPECIFIED+0
UNSPECIFIED+Size1 wrap_content UNSPECIFIED+0

ok 下面說說layout 方法:

關于Layout的方法有:  public void layout(int l, int t, int r, int b)      這個方法是View 的方法  protected abstract void onLayout(boolean changed,int l, int t, int r, int b);     ViewGroup的方法且 ViewGroup 必須實作該方法

public void layout(int l, int t, int r, int b)中得  l、t、r、b是指view的左left、上top、右right、底bottom的位置

View 的具體位置是有 ViewGroup中得 onLayout 方法決定 在ViewGroup中的onLayout方法需要依次的周遊子View 調用他們的Layout方法 下面可以看看 LinearLayout 的onLayout方法

@Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
      if (mOrientation == VERTICAL) {
          layoutVertical();
      } else {
          layoutHorizontal();
      }
  }
void layoutVertical() {
      final int paddingLeft = mPaddingLeft;

      int childTop;
      int childLeft;

      // Where right end of child should go
      final int width = mRight - mLeft;
      int childRight = width - mPaddingRight;

      // Space available for child
      int childSpace = width - paddingLeft - mPaddingRight;

      final int count = getVirtualChildCount();

      final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
      final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

      switch (majorGravity) {
         case Gravity.BOTTOM:
             // mTotalLength contains the padding already
             childTop = mPaddingTop + mBottom - mTop - mTotalLength;
             break;

             // mTotalLength contains the padding already
         case Gravity.CENTER_VERTICAL:
             childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
             break;

         case Gravity.TOP:
         default:
             childTop = mPaddingTop;
             break;
      }

      for (int i = 0; i < count; i++) {
          final View child = getVirtualChildAt(i);
          if (child == null) {
              childTop += measureNullChild(i);
          } else if (child.getVisibility() != GONE) {
              final int childWidth = child.getMeasuredWidth();
              final int childHeight = child.getMeasuredHeight();

              final LinearLayout.LayoutParams lp =
                      (LinearLayout.LayoutParams) child.getLayoutParams();

              int gravity = lp.gravity;
              if (gravity < 0) {
                  gravity = minorGravity;
              }
              final int layoutDirection = getLayoutDirection();
              final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
              switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                  case Gravity.CENTER_HORIZONTAL:
                      childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                              + lp.leftMargin - lp.rightMargin;
                      break;

                  case Gravity.RIGHT:
                      childLeft = childRight - childWidth - lp.rightMargin;
                      break;

                  case Gravity.LEFT:
                  default:
                      childLeft = paddingLeft + lp.leftMargin;
                      break;
              }

              if (hasDividerBeforeChildAt(i)) {
                  childTop += mDividerHeight;
              }

              childTop += lp.topMargin;
              setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                      childWidth, childHeight);
              childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

              i += getChildrenSkipCount(child, i);
          }
      }
  }
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}
           

在Layout中做的就是 計算好位置, 然後調用子ViewLayout方法

下面在來看看Draw 的過程, 當測量完了, 寬高弄好了, 位置也确定了, 最後就是 畫View 了 draw 相關的方法有; public void draw(Canvas canvas) protected void onDraw(Canvas canvas)

View 的Draw 方法中有6個步驟 

/*
        * 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)
        */
           

在第三步 Draw view's content 調用onDraw方法,子類中實作onDraw方法。 在第四步,Draw children步驟使用的dispatchDraw方法,這個方法在ViewGroup中有實作。     View或ViewGroup的子類不用再重載ViewGroup中該方法,因為它已經有了預設而且标準的view系統流程。dispatchDraw()内部for循環調用drawChild()分别繪制每一個子視圖,而drawChild()内部又會調用draw()函數完成子視圖的内部繪制工作

ok 上面基本就是 繪制View 的一個大概的粗擦的流程 寫的不是很好, 但是謝了總歸會加深寫印象,  一個對源碼有更深的探究時, 還有在重新寫一遍 産考一下文章: http://blog.csdn.net/xyz_lmn/article/details/20385049