美文网首页
View工作原理之layout

View工作原理之layout

作者: 李die喋 | 来源:发表于2019-08-09 15:10 被阅读0次

    layout的调用过程

    view的三大流程是从performTravsals()开始的,我们从这里看起

    ViewRootImpl#performTraversals()

    performTraversals()调用performLayout()

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        //lp是窗口的LayoutParams 和 窗口的宽高
        performLayout(lp, mWidth, mHeight);
        ......
    }
    

    ViewRootImpl#performLayout()

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
        //host就是decroview
        final View host = mView;
        if (host == null) {
            return;
        }
         try {
            //传入decroview的四个顶点
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            ......
        }
    }
    

    ViewGroup#layout()

    当我们定位到layout方法时,它进入的是view的layout方法,因为ViewGroup是继承自View的,我们先看它的layout方法,发现layout是用final修饰的,也就是ViewGroup的layout方法是不能被继承的

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

    View#layout()

    调用layout方法,确定view本身的位置

    public void layout(int l, int t, int r, int b) {
        ......
        //通过setFrame方法来确定View的四个顶点的位置
        boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        
        //调用onLayout方法 父容器确定子元素的位置
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);
            ......
        }
        ......
    }
    

    setFrame()

    通过确定view的宽高是否发生改变,来修改mLeft mRight mTop mBottom这四个确定view位置的值

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        
        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;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
                mPrivateFlags |= PFLAG_HAS_BOUNDS;
    
    
                if (sizeChanged) {
                    sizeChange(newWidth, newHeight, oldWidth, oldHeight);
                }
            ......
        }
    }
    

    View#onLayout()

    View的onLayout方法是空实现,需要我们人为去实现。ViewGroup的onLayout是一个抽象方法,在我们自定义view的时候需要去重写。

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

    我们进入FrameLayout的onLayout方法中去看看

    FrameLayout#onLayout()

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        //把父容器的参数传进
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    

    FrameLayout#layoutChildren()

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        //获取子view的个数
        final int count = getChildCount();
        
        //下面四个值会影响到子view的布局
        //parentLeft由父容器的padding和foreground决定
        final int parentLeft = getPaddingLeftWithForeground();
        //parentRight由父容器的width、padding、foreground决定
        final int parentRight = right - left - getPaddingRightWithForeground();
    
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        
        //遍历子元素
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                
                //获取子view测量的宽高
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
    
                int childLeft;
                int childTop;
    
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
    
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    
                //当子view设置了水平方向上的layout_gravity
                //childLeft是子view的左上角的值
                switch (absoluteGravity &
                Gravity.HORIZONTAL_GRAVITY_MASK) {
                    //当子view居中显示,childLeft = (父容器的宽度 - 子view的宽度)/2 + 父view左上角的距离 + 若有margin,左margin - 右margin
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    //水平居右,childLeft = parentRight - 子view宽度-子view的rightMargin
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    //水平居左 childLeft = parentLeft+子view的margin
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                
                //当子view设置了水平方向上的layout_gravity,如上所示
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                //调用layout确定子view本身的位置
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    

    总结

    view的layout过程

    • 从ViewRootImpl的performTraversals()方法开始调用perforLayout()方法
    • performLayout方法调用layout方法,通过确定mLeft、mTop、mRight、mBottom四个值确定view的位置
    • 再调用onLayout去确定子view的位置

    相关文章

      网友评论

          本文标题:View工作原理之layout

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