美文网首页
学个明白--View的Layout

学个明白--View的Layout

作者: ImTudou | 来源:发表于2017-12-02 15:33 被阅读31次

    上一篇写了View的测量学个明白--View的测量
    这一篇写View的layout过程。

    这一篇主要是总结View的Layout过程。
    我们在进行View的Layout的时候,直接调用View的layout方法。

    layout(int l, int t, int r, int b)
    // left,top, right,bottom
    

    但是里面发生了什么呢?

    我在学习了查阅了资料后写下了这篇文章,主要内容主要摘录自《Android开发艺术探索》和源码,加入自己的见解和理解。本人才疏学浅,不恰当之处望批评指正。

    Layout过程

    Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout的方法,在layout方法中onLayout方法又会被调用。Layout过程和measure过程相比就简单多了,layout方法确定View本身的位置而onLayout方法则会确定所有子元素的位置。
    View的layout()方法

    //android-25 layout方法
     /**
         * Assign a size and position to a view and all of its
         * descendants
         *
         * <p>This is the second phase of the layout mechanism.
         * (The first is measuring). In this phase, each parent calls
         * layout on all of its children to position them.
         * This is typically done using the child measurements
         * that were stored in the measure pass().</p>
         *
         * <p>Derived classes should not override this method.
         * Derived classes with children should override
         * onLayout. In that method, they should
         * call layout on each of their children.</p>
         *
         * @param l Left position, relative to parent
         * @param t Top position, relative to parent
         * @param r Right position, relative to parent
         * @param b Bottom position, relative to parent
         */
    @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<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;
        }
    

    大致流程如下:首先会通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft, mTopm,Rightm,Bottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了;
    接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置。
    和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关系,所以View和ViewGroup均没有真正实现。

     /**
         * Called from layout when this view should
         * assign a size and position to each of its children.
         *
         * Derived classes with children should override
         * this method and call layout on each of
         * their children.
         * @param changed This is a new size or position for this view
         * @param left Left position, relative to parent
         * @param top Top position, relative to parent
         * @param right Right position, relative to parent
         * @param bottom Bottom position, relative to parent
         */
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    我们来看看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);
            }
        }
    

    下面贴出layoutVertical(), layoutHorizontal()代码:(似乎纯属占篇幅)

    /**
         * Position the children during a layout pass if the orientation of this
         * LinearLayout is set to {@link #VERTICAL}.
         *
         * @see #getOrientation()
         * @see #setOrientation(int)
         * @see #onLayout(boolean, int, int, int, int)
         * @param left
         * @param top
         * @param right
         * @param bottom
         */
        void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
    
            int childTop;
            int childLeft;
            
            // Where right end of child should go
            final int width = right - left;
            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 + bottom - top - mTotalLength;
                   break;
    
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (bottom - top - 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);
                }
            }
        }
    
    /**
         * Position the children during a layout pass if the orientation of this
         * LinearLayout is set to {@link #HORIZONTAL}.
         *
         * @see #getOrientation()
         * @see #setOrientation(int)
         * @see #onLayout(boolean, int, int, int, int)
         * @param left
         * @param top
         * @param right
         * @param bottom
         */
        void layoutHorizontal(int left, int top, int right, int bottom) {
            final boolean isLayoutRtl = isLayoutRtl();
            final int paddingTop = mPaddingTop;
    
            int childTop;
            int childLeft;
            
            // Where bottom of child should go
            final int height = bottom - top;
            int childBottom = height - mPaddingBottom; 
            
            // Space available for child
            int childSpace = height - paddingTop - mPaddingBottom;
    
            final int count = getVirtualChildCount();
    
            final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    
            final boolean baselineAligned = mBaselineAligned;
    
            final int[] maxAscent = mMaxAscent;
            final int[] maxDescent = mMaxDescent;
    
            final int layoutDirection = getLayoutDirection();
            switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
                case Gravity.RIGHT:
                    // mTotalLength contains the padding already
                    childLeft = mPaddingLeft + right - left - mTotalLength;
                    break;
    
                case Gravity.CENTER_HORIZONTAL:
                    // mTotalLength contains the padding already
                    childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
                    break;
    
                case Gravity.LEFT:
                default:
                    childLeft = mPaddingLeft;
                    break;
            }
    
            int start = 0;
            int dir = 1;
            //In case of RTL, start drawing from the last child.
            if (isLayoutRtl) {
                start = count - 1;
                dir = -1;
            }
    
            for (int i = 0; i < count; i++) {
                final int childIndex = start + dir * i;
                final View child = getVirtualChildAt(childIndex);
                if (child == null) {
                    childLeft += measureNullChild(childIndex);
                } else if (child.getVisibility() != GONE) {
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
                    int childBaseline = -1;
    
                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();
    
                    if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
                        childBaseline = child.getBaseline();
                    }
                    
                    int gravity = lp.gravity;
                    if (gravity < 0) {
                        gravity = minorGravity;
                    }
                    
                    switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                        case Gravity.TOP:
                            childTop = paddingTop + lp.topMargin;
                            if (childBaseline != -1) {
                                childTop += maxAscent[INDEX_TOP] - childBaseline;
                            }
                            break;
    
                        case Gravity.CENTER_VERTICAL:
                            // Removed support for baseline alignment when layout_gravity or
                            // gravity == center_vertical. See bug #1038483.
                            // Keep the code around if we need to re-enable this feature
                            // if (childBaseline != -1) {
                            //     // Align baselines vertically only if the child is smaller than us
                            //     if (childSpace - childHeight > 0) {
                            //         childTop = paddingTop + (childSpace / 2) - childBaseline;
                            //     } else {
                            //         childTop = paddingTop + (childSpace - childHeight) / 2;
                            //     }
                            // } else {
                            childTop = paddingTop + ((childSpace - childHeight) / 2)
                                    + lp.topMargin - lp.bottomMargin;
                            break;
    
                        case Gravity.BOTTOM:
                            childTop = childBottom - childHeight - lp.bottomMargin;
                            if (childBaseline != -1) {
                                int descent = child.getMeasuredHeight() - childBaseline;
                                childTop -= (maxDescent[INDEX_BOTTOM] - descent);
                            }
                            break;
                        default:
                            childTop = paddingTop;
                            break;
                    }
    
                    if (hasDividerBeforeChildAt(childIndex)) {
                        childLeft += mDividerWidth;
                    }
    
                    childLeft += lp.leftMargin;
                    setChildFrame(child, childLeft + getLocationOffset(child), childTop,
                            childWidth, childHeight);
                    childLeft += childWidth + lp.rightMargin +
                            getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, childIndex);
                }
            }
        }
    

    (是不是看到这些代码瑟瑟发抖。只要你坚信代码是写出的就行了。(什么? AI 写代码。草,你找茬是不是?))
    高潮来了:
    简化一下layoutVertical代码,注意看// Tudou add annotation的地方

     void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
    
            int childTop;
            int childLeft;
            
            // Where right end of child should go
            final int width = right - left;
            int childRight = width - mPaddingRight;
            
            // Space available for child
            int childSpace = width - paddingLeft - mPaddingRight;
            // Tudou add annotation
            // get count
            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 + bottom - top - mTotalLength;
                   break;
    
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                   break;
    
               case Gravity.TOP:
               default:
                   childTop = mPaddingTop;
                   break;
            }
            // Tudou add annotation
            // 遍历
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    // Tudou add annotation
                    // get width,height
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
                    
                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();
                    
                   ....
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
    
                    childTop += lp.topMargin;
                    //Tudou add annotation
                    // 在setChildFrame的childWidth, childHeight,我们可以看出来,就是子元素测量宽/高.
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    简单分析一下layoutVertical的代码逻辑,此方法会遍历所有子元素调用setChildFrame方法来为子元素指定对应的位置。
    其中childTop变量会逐渐增大,这就意味着后面的子元素会被放置在靠下的位置,正好符合LinearLayout 方向是竖直方向的特点。
    setChildFrame仅仅是调用子元素的layout方法而已, 这样父元素在layout中完成自己的定位后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,这样一层一层地传递下去就完成了整个View树的layout过程。

    看一下setChildFrame

    //这里的width,height就是就是子元素测量宽/高.
     private void setChildFrame(View child, int left, int top, int width, int height) {        
           child.layout(left, top, left + width, top + height);
       }
    

    而在layout方法中会通过setFrame去设置子元素的四个顶点的位置,在setFrame中有如下赋值语句,这样一来子元素的位置就确定了。

    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    

    参考资料:
    developer.android.com
    Android开发艺术探索

    相关文章

      网友评论

          本文标题:学个明白--View的Layout

          本文链接:https://www.haomeiwen.com/subject/zyxgbxtx.html