zoukankan      html  css  js  c++  java
  • 【Android

      layout(布局)的作用是ViewGroup用来确定子元素的位置,在这个过程中会用到两个核心方法: layout() 和 onLayout() 。layout()方法用来确定View本身的位置,onLayout()方法则用来确定所有子元素的位置。View和ViewGroup中都有layout()和onLayout()两个方法,但两个类中都没有实现onLayout(),其原因和ViewGroup中没有onMeasure()方法是相同的:因为不同ViewGroup的子类对布局的要求不一样。

      当我们自定义了一个ViewGroup的时候,会先确定这个ViewGroup的位置,然后,通过重写 onLayout() 方法,遍历所有的子元素并调用其 layout() 方法,在layout()方法中onLayout()方法又会被调用。ViewGroup就是通过这个过程,递归地对所有子View进行了布局。来看一下View类中的layout()方法的源码:

    /**
     * 本方法用来给一个View和它的所有子View设置尺寸和位置;
     * 这是Android布局机制的第二个阶段(第一个阶段是测量);
     * 在这个阶段中,每个父容器都调用layout()方法来定位它的子View;
     * 子类不能重写这个方法,而应该重写onLayout()方法;
     * 在onLayout()方法中调用layout()方法来设置每个子View的位置。
     *
     * @param l 相对于父容器左边的距离
     * @param t 相对于父容器上边的距离
     * @param r 相对于父容器右边的距离
     * @param b 相对于父容器下边的距离
     */
    @SuppressWarnings({"unchecked"})
    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);
    
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
    
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<View.OnLayoutChangeListener> listenersCopy =
                        (ArrayList<View.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;
    }

      从源码中可以看出这个方法的大致流程:首先通过 setFrame() 方法来设置View的四个位置元素的位置,即初始化mLeft、mTop、mRight和mBottom这四个值。View的四个顶点一旦确定,那么View在父容器中的位置也就确定了;接着会调用 onLayout() 方法,这个方法的用途是父容器确定子元素的位置。

      上面提到,ViewGroup中没有实现onLayout()方法,原因是不同ViewGroup子类的布局方式不同,因此onLayout()的具体实现需要根据这个ViewGroup子类的布局方式来确定。比如,LinearLayout的onLayout()方法的源码如下:

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

      在自定义布局的时候,我们的任务就是:遍历所有的子元素,确定它们的大小和位置(大小主要是通过 getMeasuredWidth() 和 getMeasuredHeight() 两个方法,取出在 onMeasure() 方法中测量得到的宽/高;位置需要自行设置),然后调用 view.layout() 方法或直接调用ViewGroup中的方法 setChildFrame() 方法(setChildFrame()方法内部调用的就是view.layout()方法),将子元素布局到这个ViewGroup中。

      最后还需要说明一点,“测量宽/高” 和 “最终宽/高” 是两个不同的概念。测量宽/高是在onMeasure()方法中测量得到的宽度或高度,而最终宽/高是在onLayout()方法中最终放置的子元素的宽度或高度。在View的默认实现中,View的测量宽/高和最终宽/高是相等的,但是测量宽/高的赋值时机较早。

  • 相关阅读:
    hdu 3790 最短路径问题
    hdu 2112 HDU Today
    最短路问题 以hdu1874为例
    hdu 1690 Bus System Floyd
    hdu 2066 一个人的旅行
    hdu 2680 Choose the best route
    hdu 1596 find the safest road
    hdu 1869 六度分离
    hdu 3339 In Action
    序列化和反序列化
  • 原文地址:https://www.cnblogs.com/itgungnir/p/6721868.html
Copyright © 2011-2022 走看看