美文网首页Android view体系、屏幕适配、显示相关Android进阶我爱编程
Android高级进阶——View的工作原理(二)Layout过

Android高级进阶——View的工作原理(二)Layout过

作者: aKaiC | 来源:发表于2018-04-12 20:22 被阅读16次

    开篇:
    上一篇已经了解了 View 的工作原理之 Measure 过程,了解到 Measure 过程是从 ViewRootImpl#performTraversals 开始的,最后会执行到 onMeasure 方法,也对自定义 View 时 margin、padding 以及 wrap_content 如何处理已经简单说明,下面开始 Layout 过程。

    还是从 ViewRootImpl#performTraversals 方法开始,不过这次我们要看的是 performLayout(lp, mWidth, mHeight) 方法了

    ViewRootImpl#performTraversals 方法开始

    private void performTraversals() {
             ...
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                            (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                            || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                            updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    
                    // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
    
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);
                ...
    
            if (!cancelDraw && !newSurface) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }
    
                performDraw();
                ...
    

    直接看 performLayout 方法

        private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            final View host = mView;
            if (host == null) {
                return;
            }
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    

    最终调用了 View 的 Layout 方法,并传递当前 View 的四个顶点位置

    layout 方法

    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;
            //如果 isLayoutModeOptical 方法返回 true,那么就会去执行 setOpticalFrame 方法,否则就会去执行 setFrame 方法,并且 setOpticalFrame 内部其实还是调用的 setFrame 方法,所以无论如何都会执行 setFrame 方法,而 setFrame 方法会将传递的 left、top、right、bottom 存储在 View 的成员变量中,并且返回一个布尔值,如果返回true,表示 View 的位置或尺寸发生了变化,否则表示未发生变化,后面会单独介绍
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
            //如果 View 的位置发生了变化那么就会去调用 View 的 onLayout 方法,View 的 onLayout 方法默认是一个空实现
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);
    

    看下 View 的 onLayout 方法,是一个空方法,具体实现是在子类中

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    来看一下 setFrame 方法,完事了开始介绍 ViewGroup 的 Layout

    setFrame 方法

    setFrame 方法具体是用来指定 View 的大小以及位置的,将传递过来的 left、top、right、bottom 都存储在 View 的成员变量中,并返回一个布尔值来标识 View 的位置或尺寸是否发生了变化

    protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
            //将新旧 left、top、right、bottom 进行比较,如果不完全相同则表示 View 的位置发生了改变,使用变量 changed 记录
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // Remember our drawn bit
                int drawn = mPrivateFlags & PFLAG_DRAWN;
                //获取新与旧的宽度和高度
                int oldWidth = mRight - mLeft;
                int oldHeight = mBottom - mTop;
                int newWidth = right - left;
                int newHeight = bottom - top;
                //比较新旧的 width 和 height 是否相同,不同则表示 View 的尺寸发生了变化
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
                //将新的 left、top、right、bottom 进行存储
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
                mPrivateFlags |= PFLAG_HAS_BOUNDS;
    
                //如果尺寸发生了变化,就调用 sizeChange 方法,该方法又会调用 onSizeChanged 方法,传递 View 的新旧尺寸
                if (sizeChanged) {
                    sizeChange(newWidth, newHeight, oldWidth, oldHeight);
                }
    
                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                    // If we are visible, force the DRAWN bit to on so that
                    // this invalidate will go through (at least to our parent).
                    // This is because someone may have invalidated this view
                    // before this call to setFrame came in, thereby clearing
                    // the DRAWN bit.
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(sizeChanged);
                    // parent display list may need to be recreated based on a change in the bounds
                    // of any child
                    invalidateParentCaches();
                }
    
                // Reset drawn bit to original value (invalidate turns it off)
                mPrivateFlags |= drawn;
    
                mBackgroundSizeChanged = true;
                mDefaultFocusHighlightSizeChanged = true;
                if (mForegroundInfo != null) {
                    mForegroundInfo.mBoundsChanged = true;
                }
    
                notifySubtreeAccessibilityStateChangedIfNeeded();
            }
            return changed;
        }
    

    ViewGroup的 Layout 过程

    前面已经说过,Layout 过程的具体实现其实是在 onLayout 方法中,而 ViewGroup 继承自 View ,那它肯定也实现了 onLayout 方法,我们来看一下:

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

    居然是一个抽象方法,为什么是抽象方法其实在介绍 Measure 过程时就已经说过了,因为不同的 ViewGroup 肯定是有各自的实现,不同的实现就让具体的 View 的实现不就行了,所以我们还是要看 LinearLayout 的 onLayout 方法

    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 方法

    LinearLayout # layoutVertical

    layoutVertical 方法比起 measureVertical 代码量上就少了好多了,才 100 行不到,我们一步一步来分析吧

    void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
            //子 View 布局时使用的 top 值
            int childTop;
            //子 View 布局时使用的 left 值
            int childLeft;
    
            // Where right end of child should go
            // 当前 view 的最大宽度
            final int width = right - left;
            //减去 paddingRight 后剩余的 width
            int childRight = width - mPaddingRight;
    
            // Space available for child
            //除去leftPadding以及rightPadding后剩余的宽度
            int childSpace = width - paddingLeft - mPaddingRight;
            //得到子 View 的数量
            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;
            }
            //遍历子 View
            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:
                            //计算当前子 View 的 left 位置
                            childLeft = paddingLeft + lp.leftMargin;
                            break;
                    }
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
                    //当前子 View 的 top 值
                    childTop += lp.topMargin;
                    //调用子 view 的 layout 方法
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    setChildFrame 方法

    public void setOrientation(@OrientationMode int orientation) {
            if (mOrientation != orientation) {
                mOrientation = orientation;
                requestLayout();
            }
        }
    

    完事,Layout 过程是这三个过程中最简单的一个,没什么需要多说的....

    相关文章

      网友评论

        本文标题:Android高级进阶——View的工作原理(二)Layout过

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