美文网首页
Layout过程

Layout过程

作者: Utte | 来源:发表于2018-08-07 22:00 被阅读56次

    一、调用流程

    Layout流程也是从perfromTravrsals开始,在调用完测量流程后,又调用了performLayout(),这就是Layout流程的起点。

    1. ViewRootImpl # performTraversals()

    performTraversals()调用了performLayout()。

    // ...
    if (didLayout) {
        // 参数分别是窗口的params和窗口的测量宽高
        performLayout(lp, mWidth, mHeight);
        // ...
    }
    // ...
    
    2. ViewRootImpl # performLayout()

    performLayout()调用了DecorView的layout()。

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        // ...
        // host就是DecorView
        final View host = mView;
        if (host == null) {
            return;
        }
        
        // ...
        
        try {
            // 调用DecorView的layout(),传入窗口四个顶点的坐标。
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            
            // ...
            
        } finally {
            // ...
        }
        mInLayout = false;
    }
    
    3. ViewGroup # layout()

    DecorView自身并没有重写layout(),它的父类FrameLayout也没有,所以其实调用的是ViewGroup的layout()。和measure()类似的是,这也是一个final的方法 ,将窗口的顶点坐标传递到View,调用了View的layout()。

    @Override
    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;
        }
    }
    
    4. View # layout()

    layout()中会先调用setFrame()将传入的窗口坐标设置到全局变量,再调用onLayout()去分发layout。

    public void layout(int l, int t, int r, int b) {
        // ...
        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);
            // ...
        }
        // ...
    }
    
    5. DecorView # onLayout()

    可以写一个demo做下实验,经过上面的步骤,onLayout()调用的确实是DecorView的onLayout()。调用了父类(FrameLayout)的onLayout()。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // ...
    }
    
    6. FrameLayout # onLayout()

    FrameLayout的onLayout()简单的调用勒兹的layoutChildren(),从方法名也能看出开始分发处理子View的layout了。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    
    7. FrameLayout # layoutChildren()

    在这个方法里完成了layout的分发。遍历子View,调用子View的layout()。

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        
        // ...
        
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
                
                // 从水平和竖直方向对变量做处理...
                
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    

    二、View和ViewGroup中的相关方法

    View # layout()
    • 重新设置了最新的四个相对坐标。
    • 调用onDraw()。
    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;
        }
        // mXXX是此View相对于父View的相对坐标
        // 先保存下来
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        // setFrame()将mXXX变为传入的参数
        // 四个顶点确定了,View的位置也就确定了
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 调用onLayout()
            onLayout(changed, l, t, r, b);
            // ...
        }
        // ...
    }
    
    View # setFrame()

    实际上就是重新赋值mLeft、mTop、mRight、mBottom,

    protected boolean setFrame(int left, int top, int right, int bottom) {
        // ...
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            // ...
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            // ...
            }
            // ...
        }
        return changed;
    }
    
    View # onLayout()

    View中的onLayout()是空实现,具体实现和具体布局有关,所以没有做统一实现。

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

    ViewGroup中并没有做很多工作,而且是一个final的方法,调用了View的layout()。

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

    ViewGroup中的onLayout()是一个抽象方法,ViewGroup是一个抽象类,所以所以继承它的类都需要实现onLayout()。

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

    三、测量宽高和实际宽高。

    1. 获取方式

    测量宽高:

    width = view.getMeasuredWidth();
    height = view.getMeasuredHeight();
    
    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
    

    实际宽高

    width = view.getWidth();
    height = view.getHeight();
    
    public final int getWidth() {
        return mRight - mLeft;
    }
    
    2. 异同点

    同:正常情况下测量宽高与实际宽高相等。

    mLeft、mRight、mTop、mBottom这四个值是从layout()的参数而来的,而大部分调用layout时,传的参数都是根据getMeasureWidth()和getMeasureHeight()来决定的,所以正常情况下都有mRight - mLeft == mMeasureWidth & MEASURED_SIZE_MASK,除非修改layout()参数计算的规则。

    异:确定时机不同。

    测量宽高在measure过程中形成,而实际宽高是在layout赋值mXXX四个顶点坐标后形成的。

    相关文章

      网友评论

          本文标题:Layout过程

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