首页 > 代码库 > [Android FrameWork 6.0源码学习] View的重绘过程之Layout

[Android FrameWork 6.0源码学习] View的重绘过程之Layout

View绘制的三部曲,测量,布局,绘画
现在我们分析布局部分
测量部分在上篇文章中已经分析过了。不了解的可以去我的博客里找一下

View的布局和测量一样,都是从ViewRootImpl中发起,ViewRootImpl先通过measure来初始化整个的view树
之后会调用onLayout方法来布局,ViewRootImpl是通过performLayout函数来发起重绘的
比较重要的部分我会写注释,注意看注释就行

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,            int desiredWindowHeight) {        mLayoutRequested = false;        mScrollMayChange = true;        mInLayout = true;        final View host = mView;        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {            Log.v(TAG, "Laying out " + host + " to (" +                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");        }        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");        try {            //通过调用DecorView的layout函数,来发起整个view视图的重绘            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());            mInLayout = false;            int numViewsRequestingLayout = mLayoutRequesters.size();            if (numViewsRequestingLayout > 0) {                // requestLayout() was called during layout.                // If no layout-request flags are set on the requesting views, there is no problem.                // If some requests are still pending, then we need to clear those flags and do                // a full request/measure/layout pass to handle this situation.                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,                        false);                if (validLayoutRequesters != null) {                    // Set this flag to indicate that any further requests are happening during                    // the second pass, which may result in posting those requests to the next                    // frame instead                    mHandlingLayoutInLayoutRequest = true;                    // Process fresh layout requests, then measure and layout                    int numValidRequests = validLayoutRequesters.size();                    for (int i = 0; i < numValidRequests; ++i) {                        final View view = validLayoutRequesters.get(i);                        Log.w("View", "requestLayout() improperly called by " + view +                                " during layout: running second layout pass");                        view.requestLayout();                    }                    measureHierarchy(host, lp, mView.getContext().getResources(),                            desiredWindowWidth, desiredWindowHeight);                    mInLayout = true;                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());                    mHandlingLayoutInLayoutRequest = false;                    // Check the valid requests again, this time without checking/clearing the                    // layout flags, since requests happening during the second pass get noop‘d                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);                    if (validLayoutRequesters != null) {                        final ArrayList<View> finalRequesters = validLayoutRequesters;                        // Post second-pass requests to the next frame                        getRunQueue().post(new Runnable() {                            @Override                            public void run() {                                int numValidRequests = finalRequesters.size();                                for (int i = 0; i < numValidRequests; ++i) {                                    final View view = finalRequesters.get(i);                                    Log.w("View", "requestLayout() improperly called by " + view +                                            " during second layout pass: posting in next frame");                                    view.requestLayout();                                }                            }                        });                    }                }            }        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }        mInLayout = false;    }

这个函数主要功能就是调用view的layout方法,接下来要分析的就是layout函数了。这个函数在View中,是触发onLayout函数的方法

 

    @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;                //判断是否使用 optical bound 布局,并且绘制Frame出来        boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);        //如果需要重新layout的话,就开始调用DecorView的onLayout方法,我们简单看一下        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {            onLayout(changed, l, t, r, b);            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;    }

这个函数的工作就是分发整个的布局流程,先是DecorView,在FrameLayout ....直到整个view tree布局完毕

 

        @Override        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {            super.onLayout(changed, left, top, right, bottom);            //获取界面的边框如果有偏移,就需要偏移一下view窗口            getOutsets(mOutsets);            if (mOutsets.left > 0) {                offsetLeftAndRight(-mOutsets.left);            }            if (mOutsets.top > 0) {                offsetTopAndBottom(-mOutsets.top);            }        }

这个onLayout是在DecorView中,他调用了super,也就是FrameLayout下边的onLayout方法,我们在继续看FrameLayout

 

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

这个函数页很简单,直接调用了layoutChildren方法去布局各种子view

    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();        //开始布局,目前这个是FrameLayout,特性就是默认左上角,且会z轴覆盖        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;                }                //布局子view,以此类推,会布局完整个view树                child.layout(childLeft, childTop, childLeft + width, childTop + height);            }        }    }

上面方法运行完后,整个的布局过程就结束了。view这块的设计非常棒,采用了组合模式去设计,在上边循环中去调用layout方法,layout在去触发子view的onLayout来按照各自的规则去布局,直到整个view树循环完毕

[Android FrameWork 6.0源码学习] View的重绘过程之Layout