美文网首页
源码分析UI绘制三部曲之layout

源码分析UI绘制三部曲之layout

作者: Joker_Wan | 来源:发表于2019-12-09 12:58 被阅读0次

    众所周知,UI绘制三部曲是measure、layout、draw

    本篇我们分析View#performLayout

        private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
            mLayoutRequested = false;
            mScrollMayChange = true;
            mInLayout = true;
    
            final View host = mView;
            if (host == null) {
                return;
            }
            
            ...
            
            try {
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
                ...
                
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            mInLayout = false;
        }
    

    方法的第一个参数lp是顶层布局容器的布局属性,后面两个参数mWidth,mHeight是顶层布局容器的宽高,将顶层布局容器mView赋值给host,mView为DecorView,后面接着调用host.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;
    
            // 注释1
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            
                // 注释2
                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);
                    }
                }
            }
    
            final boolean wasLayoutValid = isLayoutValid();
    
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    
            if (!wasLayoutValid && isFocused()) {
                mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
                if (canTakeFocus()) {
                    // We have a robust focus, so parents should no longer be wanting focus.
                    clearParentsWantFocus();
                } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
                    // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
                    // layout. In this case, there's no guarantee that parent layouts will be evaluated
                    // and thus the safest action is to clear focus here.
                    clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                    clearParentsWantFocus();
                } else if (!hasParentWantsFocus()) {
                    // original requestFocus was likely on this view directly, so just clear focus
                    clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                }
                // otherwise, we let parents handle re-assigning focus during their layout passes.
            } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
                mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
                View focused = findFocus();
                if (focused != null) {
                    // Try to restore focus as close as possible to our starting focus.
                    if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                        // Give up and clear focus once we've reached the top-most parent which wants
                        // focus.
                        focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                    }
                }
            }
    
            if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
                mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
                notifyEnterOrExitForAutoFillIfNeeded(true);
            }
        }
    

    看到注释1处,调用了setFrame(l, t, r, b)来确定自身的左上右下的位置,看到注释2处,接着调用了onLayout方法,继续跟进View#onLayout

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

    发现是一个空方法,供子类实现,如果当前View是一个容器,则需要重写onLayout方法来确定子View的位置,如果是子View,则不需要重写onLayout方法,我们以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) {
            final int count = getChildCount();
    
            final int parentLeft = getPaddingLeftWithForeground();
            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();
    
                    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;
    
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            }
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }
    
                    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;
                    }
    
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
        }
    

    可以看到这方法就是通过for循环去遍历子View,然后调用child.layout来递归对子View进行位置摆放

    我们来总结下,View的布局是调用view.layout确定自身的位置,即mLeft,mTop,mRight,mBottom的值,如果是ViewGroup类型,则需要调用onLayout确定子View的位置

    相关文章

      网友评论

          本文标题:源码分析UI绘制三部曲之layout

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