ViewGroup的職能
Google官網(wǎng)上給出的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.
簡(jiǎn)單來(lái)說(shuō),ViewGroup是一個(gè)包含其他view(稱之為子View)的特殊View。ViewGroup是布局和view容器的基類。ViewGroup中定義了ViewGroup.LayoutParams作為布局參數(shù)的基類。
也就是說(shuō),ViewGroup的LayoutParams非常重要。
LayoutParams
LayoutParams are used by views to tell their parents how they want to be laid out.
LayoutParams 是view用來(lái)告訴它們的父布局,它們想如何布局的。
LayoutParams 有很多子類,如下圖所示。比如MarginLayoutParams,則表明該ViewGroup支持margin,當(dāng)然這個(gè)也可以沒(méi)有。我們熟悉的LinearLayout.LayoutParams,RelativeLayout.LayoutParams也在其中。
總得說(shuō)來(lái),LayoutParams存儲(chǔ)了子View在加入ViewGroup中時(shí)的一些參數(shù)信息。當(dāng)我們?cè)趯?xiě)xml文件的時(shí)候,當(dāng)在LinearLayout中寫(xiě)childView的時(shí)候,我們可以寫(xiě)layout_gravity,layout_weight等屬性,但是到了RelativeLayout中,childView就沒(méi)有了,為什么呢?
這是因?yàn)槊總€(gè)ViewGroup需要指定一個(gè)LayoutParams,用于確定支持childView支持哪些屬性,比如LinearLayout指定LinearLayout.LayoutParams等。
所以,我們?cè)谧远xViewGroup的時(shí)候,一般也需要新建一個(gè)新的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已經(jīng)有了,那么如何讓我們自定義的ViewGroup使用我們自定義的LayoutParams類來(lái)添加子View呢,答案是重寫(xiě)generateLayoutParams返回我們自定義的LayoutParams對(duì)象即可。
@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大致有三個(gè)步驟measure,layout,draw
- Measure
對(duì)于ViewGroup來(lái)說(shuō),除了要完成自身的measure過(guò)程,還要遍歷完成子View的measure方法,各個(gè)子View再去遞歸地完成measure過(guò)程,但是ViewGroup被定義成了抽象類,ViewGroup里并沒(méi)有重寫(xiě)onMeasure()方法,而是提供了一個(gè)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();
}
}
可以看出,在循環(huán)對(duì)子View進(jìn)行measure過(guò)程,調(diào)用的是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()方法中通過(guò)getChildMeasureSpec()方法,獲取子View的childWidthMeasureSpec 和childHeightMeasureSpec,然后傳遞給子View進(jìn)行測(cè)量。
我們知道,ViewGroup是一個(gè)抽象類,它并沒(méi)有實(shí)現(xiàn)onMeasure()方法,所以測(cè)量過(guò)程的onMeasure()方法就要到各個(gè)子類中去實(shí)現(xiàn)。
也就是說(shuō),在我們自定義的ViewGroup中,我們需要重寫(xiě)onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
//自定義ViewGroup的測(cè)量在此添加
}
在這里面,直接調(diào)用父類的measureChildren()方法,測(cè)量所有的子控件的,讓然,自定義ViewGroup的測(cè)量還有待去完善。
- Layout
Layout的作用是ViewGroup用來(lái)確定View的位置,而這都在onLayout()方法中實(shí)現(xiàn),在ViewGroup中,onLayout()方法被定義成了抽象方法,需要在自定義ViewGroup中具體實(shí)現(xiàn)。在onLayout()方法中遍歷所有子View并調(diào)用子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)方法可以對(duì)子View的位置進(jìn)行設(shè)置,四個(gè)參數(shù)的意思大家通過(guò)變量名都應(yīng)該清楚了。
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()方法又被調(diào)用,這樣layout()方法就會(huì)一層層地調(diào)用下去,完成布局。
- Draw
draw過(guò)程是在View的draw()方法里進(jìn)行。draw地址分為以下幾個(gè)過(guò)程
(1)drawBackground(canvas):繪制背景
(2)onDraw(canvas) :繪制自己
(3) dispatchDraw(canvas):繪制children
(4)onDrawForeground(canvas):繪制裝飾 (foreground, scrollbars)
在自定義View的時(shí)候,我們不需要重寫(xiě)draw()方法,只需重寫(xiě)onDraw()方法即可。
值得注意的是ViewGroup容器組件的繪制,當(dāng)它沒(méi)有背景時(shí)直接調(diào)用的是dispatchDraw()方法, 而繞過(guò)了draw()方法,當(dāng)它有背景的時(shí)候就調(diào)用draw()方法,而draw()方法里包含了dispatchDraw()方法的調(diào)用。因此要在ViewGroup上繪制東西的時(shí)候往往重寫(xiě)的是dispatchDraw()方法而不是onDraw()方法。