自定義ViewGroup(0)

ViewGroup的職能

Google官網上給出的ViewGroup的功能如下:

*A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams
 class which serves as the base class for layouts parameters.*

簡單來說,ViewGroup是一個包含其他view(稱之爲子View)的特殊View。ViewGroup是佈局和view容器的基類。ViewGroup中定義了ViewGroup.LayoutParams作爲佈局參數的基類。

也就是說,ViewGroup的LayoutParams非常重要。

LayoutParams

LayoutParams are used by views to tell their parents how they want to be laid out.
LayoutParams 是view用來告訴它們的父佈局,它們想如何佈局的。

LayoutParams 有很多子類,如下圖所示。比如MarginLayoutParams,則表明該ViewGroup支持margin,當然這個也可以沒有。我們熟悉的LinearLayout.LayoutParamsRelativeLayout.LayoutParams也在其中。
image.png

總得說來,LayoutParams存儲了子View在加入ViewGroup中時的一些參數信息。當我們在寫xml文件的時候,當在LinearLayout中寫childView的時候,我們可以寫layout_gravity,layout_weight等屬性,但是到了RelativeLayout中,childView就沒有了,爲什麼呢?

**這是因爲每個ViewGroup需要指定一個LayoutParams,用於確定支持childView支持哪些屬性,比如LinearLayout指定LinearLayout.LayoutParams等。
所以,我們在自定義ViewGroup的時候,一般也需要新建一個新的LayoutParams類繼承自ViewGroup.LayoutParams。**

public static class LayoutParams extends ViewGroup.LayoutParams {

  public int left = 0;
  public int top = 0;

  public LayoutParams(Context arg0, AttributeSet arg1) {
      super(arg0, arg1);
  }

  public LayoutParams(int arg0, int arg1) {
      super(arg0, arg1);
  }

  public LayoutParams(android.view.ViewGroup.LayoutParams arg0) {
      super(arg0);
  }

}

自定義的LayoutParams已經有了,那麼如何讓我們自定義的ViewGroup使用我們自定義的LayoutParams類來添加子View呢,答案是重寫generateLayoutParams返回我們自定義的LayoutParams對象即可。

@Override
public android.view.ViewGroup.LayoutParams generateLayoutParams(
      AttributeSet attrs) {
  return new MyCustomView.LayoutParams(getContext(), attrs);
}

@Override
protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
  return new LayoutParams(LayoutParams.WRAP_CONTENT,
          LayoutParams.WRAP_CONTENT);
}

@Override
protected android.view.ViewGroup.LayoutParams generateLayoutParams(
      android.view.ViewGroup.LayoutParams p) {
  return new LayoutParams(p);
}

@Override
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
  return p instanceof MyCustomView.LayoutParams;
}
自定義ViewGroup的一般步驟

自定義ViewGroup大致有三個步驟measure,layout,draw
- Measure
對於ViewGroup來說,除了要完成自身的measure過程,還要遍歷完成子View的measure方法,各個子View再去遞歸地完成measure過程,但是ViewGroup被定義成了抽象類,ViewGroup裏並沒有重寫onMeasure()方法,而是提供了一個measureChildren()方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int childCount = this.getChildCount();
  for (int i = 0; i < childCount; i++) {
      View child = this.getChildAt(i);
      this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
      int cw = child.getMeasuredWidth();
      int ch = child.getMeasuredHeight();
  }
}

可以看出,在循環對子View進行measure過程,調用的是measureChild()方法。

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看出,measureChild()方法中通過getChildMeasureSpec()方法,獲取子View的childWidthMeasureSpec 和childHeightMeasureSpec,然後傳遞給子View進行測量。
我們知道,ViewGroup是一個抽象類,它並沒有實現onMeasure()方法,所以測量過程的onMeasure()方法就要到各個子類中去實現。
也就是說,在我們自定義的ViewGroup中,我們需要重寫onMeasure()方法。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        //自定義ViewGroup的測量在此添加

    }

在這裏面,直接調用父類的measureChildren()方法,測量所有的子控件的,讓然,自定義ViewGroup的測量還有待去完善。
- Layout
Layout的作用是ViewGroup用來確定View的位置,而這都在onLayout()方法中實現,在ViewGroup中,onLayout()方法被定義成了抽象方法,需要在自定義ViewGroup中具體實現。在onLayout()方法中遍歷所有子View並調用子View的layout方法完成子View的佈局。

@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
  int childCount = this.getChildCount();
  for (int i = 0; i < childCount; i++) {
      View child = this.getChildAt(i);
      LayoutParams lParams = (LayoutParams) child.getLayoutParams();
      child.layout(lParams.left, lParams.top, lParams.left + childWidth,
              lParams.top + childHeight);
  }
}

其中child.layout(left,top,right,bottom)方法可以對子View的位置進行設置,四個參數的意思大家通過變量名都應該清楚了。

 public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

可以看到,在layout()方法中, 子View的onLayout()方法又被調用,這樣layout()方法就會一層層地調用下去,完成佈局。

  • Draw
    draw過程是在View的draw()方法裏進行。draw地址分爲以下幾個過程

(1)drawBackground(canvas):繪製背景
(2)onDraw(canvas) :繪製自己
(3) dispatchDraw(canvas):繪製children
(4)onDrawForeground(canvas):繪製裝飾 (foreground, scrollbars)

在自定義View的時候,我們不需要重寫draw()方法,只需重寫onDraw()方法即可。
值得注意的是ViewGroup容器組件的繪製,當它沒有背景時直接調用的是dispatchDraw()方法, 而繞過了draw()方法,當它有背景的時候就調用draw()方法,而draw()方法裏包含了dispatchDraw()方法的調用。因此要在ViewGroup上繪製東西的時候往往重寫的是dispatchDraw()方法而不是onDraw()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章