Android进阶之路Android技术进阶Android开发

View的layout流程

2019-07-29  本文已影响3人  Chase_stars

理想是人生的太阳。 — 德莱塞

写在前面

《View的绘制流程》一篇中介绍了View如何工作,最终会调用ViewRootImpl的performTraversals()遍历View树,分别执行measure,layout和draw流程。

本篇文章来看下layout流程,layout的作用是确定元素的位置,ViewGroup中layout方法用来确定子元素的位置,View中的layout方法用来确定自身的位置。

ViewGroup的layout流程

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    @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); // 1
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
}

ViewGroup的layout方法中并没有做太多事情,而是调用super.layout(l, t, r, b),我们知道ViewGroup继承自View,所以super.layout(l, t, r, b)调用的就是其父类View的layout(l, t, r, b)函数,那么我们就直接看View的layout流程。

View的layout流程

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

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

    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;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 1

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b); // 2

             ......
          
       } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {

            ......

        }

    }

   protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        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);
            }

            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;
    }
}

首先我们来看layout()函数中注释1处调用的setFrame()函数,setFrame()内部会先计算出之前的宽和高与现在的宽和高进行比较,判断宽和高是否有改变,无论宽还是高,只要有一个改变,sizeChanged就会置为true,说明View的位置已经有变化了。接下来会给mLeft,mTop,mRight,mBottom进行赋值,并将结果changed返回。setOpticalFrame(l, t, r, b)内部最终调用的也是setFrame(l, t, r, b)函数。现在回到layout()方法,如果setFrame()函数的返回值changed为true,则会调用onLayout()方法,onLayout()方法是一个空方法,和onMeasure()方法类似, 确定位置时需要根据不同的控件有不同的实现,所以View和ViewGroup均没有实现该方法。参数l, t, r, b分别表示View从左,上,右,下距离父容器的距离。

LinearLayout的layout流程

public class LinearLayout extends ViewGroup {

    @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);
        }
    }

    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;

        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;

        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;
        }

        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:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight); // 1
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

    private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }
}

之前说过onLayout()是一个空方法,和onMeasure()类似,确定位置需要根据不同的控件右不同的实现,所以View和ViewGroup均没有实现该方法。

LinearLayout继承自ViewGroup,那么就来看下LinearLayout的onLayout()方法是如何实现的?

首先看onLayout()方法,会根据布局方向调用不同的函数,这里以垂直方向为例,会调用layoutVertical(l, t, r, b),在layoutVertical(l, t, r, b)函数中注释1处可以看到调用setChildFrame()函数,setChildFrame()函数内部调用了子元素的layout()方法,而子元素还可以再重写onLayout()方法确定位置,依此类推。因为布局方向是垂直的,所以childTop会一直增加,直到遍历完子元素,childTop就是当前的子元素到父控件顶边的距离。由此就可以推导出如果布局方向是水平的,childLeft会一直增加,直到遍历完子元素,childLeft就是当前的子元素到父控件左边的距离。

总结

View的layout()流程就这么多,是不是很简单呢?感兴趣的童鞋也可以深入了解下TextView和FrameLayout等layout()流程。

PS:本人才疏学浅,若有不足请赐教!!!

上一篇 下一篇

猜你喜欢

热点阅读