android下ViewGroup关于Layout的一点思考

 

1、ViewGroup类继承与View,因此ViewGroup拥有View的一些特性,但是ViewGoup作为容器(LinearLayout,FrameLayout)的Base类,,也拥有一些View不具有的特性;

2、ViiewGroup下面可以拥有多个子View,每个子View的大小和位置有ViewGoup调用View的Layout方法生成,而ViewGroup自身也拥有Layout方法背其Parent调用,再我们自己定义的ViewGroup中,OnLayout必须被实现,而OnMeasure不是必须被实现,因为在调用ViewGroup的OnLayout方法生成子View的大小位置时,不一定非要调用OnMearsure,OnMeasure只是子View期望得到的大小;

下面我们看看View的Layout的源码:

public void layout(int l, int t, int r, int b) { 
        int oldL = mLeft; 
        int oldT = mTop; 
        int oldB = mBottom; 
        int oldR = mRight; 
        boolean changed = setFrame(l, t, r, b); 
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { 
            if (ViewDebug.TRACE_HIERARCHY) { 
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); 
            } 
 
            onLayout(changed, l, t, r, b); 
            mPrivateFlags &= ~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 &= ~FORCE_LAYOUT; 
    }


代码很明显,过程比较明显:首先调用自身的setFrame方法刷新View的界面,顺便产生新的mLeft、mTop、mBottom、mRight的值,然后调用自身的OnLayout方法,此方法对View来说是空方法,只有当ViewGroup或者继承于ViewGroup的类时才会调用此方法刷新子View,最后回调所有注册过的OnLayoutChangeListener的nLayoutChange;

 

再来看下ViewGroup的Layout方法;

 

 public final void layout(int l, int t, int r, int b) {
        if (mTransition == null || !mTransition.isChangingLayout()) {
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutSuppressed = true;
        }
    }


如上面所示:首先判断是否动画是否为空或者是否执行完毕,是的话就回调View的Layout方法,View的Layout会回调到ViewGroup的OnLayout方法;

 

再看ViewGroup的OnLayout方法:

 protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);


ViewGroup的OnLayout方法是abstract,也就是说ViewGroup类只能用来被继承,无法实例化,并且其子类必须重载onLayout函数,而重载onLayout的目的就是安排其children在父视图的具体位置;重载onLayout通常做法就是起一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

那layout(l, t, r, b)中的4个参数l, t, r, b如何来确定呢?联想到之前的measure过程,measure过程的最终结果就是确定了每个视图的mMeasuredWidth和mMeasuredHeight,这两个参数可以简单理解为视图期望在屏幕上显示的宽和高,而这两个参数为layout过程提供了一个很重要的依据(但不是必须的),为了说明这个过程,我们来看下LinearLayout的layout过程:

void layoutVertical() { 
        …… 
        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(); 
                …… 
                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); 
    }


从setChildFrame可以看到LinearLayout中的子视图的右边界等于left + width,下边界等于top+height,也就是说在LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值。
     

layout过程必须要依靠measure计算出来的mMeasuredWidth和mMeasuredHeight来决定视图的显示大小吗?事实并非如此,layout过程中的4个参数l, t, r, b完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定,measure过程得到的mMeasuredWidth和mMeasuredHeight提供了视图大小的值,但我们完全可以不使用这两个值,可见measure过程并不是必须的。

 

说到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、getMeasuredHeight()返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值,看View.java中的源码便一清二楚了:

 

public final int getMeasuredWidth() { 
        return mMeasuredWidth & MEASURED_SIZE_MASK; 
    } 
public final int getWidth() { 
        return mRight - mLeft; 
    } 


这也解释了为什么有些情况下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值。

      总结:整个layout过程比较容易理解,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值