深入理解 Android 之 View 的绘制流程(三)_Layout

上篇介绍了ViewRootImpl调用View的测量操作,下面就开始介绍ViewRootImpl中的布局操作了。我们还是从ViewRootImpl中的performLayout开始。

ViewRootImpl#performLayout

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;  //标记开始布局

        final View host = mView;
        . . . . . . . . .

            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        . . . . . . . . .

        mInLayout = false;  //标记布局结束
}

该方法中的核心方法是调用View中的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;
        }
        // 保存上次View的四个位置
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        // 设置当前视图View的左,顶,右,底的位置,并且判断布局是否有改变
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //如果布局有改变,则视图View重新布局
        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;
}

方法中,首先判断是否需要重新进行测量,然后保存布局的四个位置。调用setFrame方法来设置View的布局位置。

View#setFrame

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);
     //保存当前View的最新位置
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

            //如果当前View的尺寸有所变化
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
            . . . . . . . . .
        }
        return changed;
}

方法中判断当前View的视图的位置与上次不一致时,则View会重新布局。调用方法invalidate
清除上次的位置,然后保存最新的View的位置。
完成setFrame来设置view的位置之后,就会继续调用onLayout(changed, l, t, r, b)方法。

View#onLayout

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

可以看到该方法是个空方法,具体的实现是在子类中。我们知道DecorView是继承自FrameLayout类,我们从该类中查看:

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

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();
        //遍历子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //过滤掉Visibility为Gone的情况
            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;

                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的布局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
}

从上面的代码中分析:布局中的具体逻辑实现是有ViewGroup父View来实现的。遍历获得子View的宽高和位置,然后调用child.layout对子视图View进行布局操作。
总结:
1.视图View的布局逻辑(onLayout)是由父View,也就是ViewGroup的具体子类来实现的。因此,我们如果自定义View一般都无需重写onMeasure方法,但是如果自定义一个ViewGroup的话,就必须实现onLayout方法,因为该方法在ViewGroup是抽象的,所有ViewGroup的所有子类必须实现onLayout方法。
2.当我们的视图View在布局中使用 android:visibility=”gone” 属性时,是不占据屏幕空间的,因为在布局时ViewGroup会遍历每个子视图View,判断当前子视图View是否设置了 Visibility==GONE,如果设置了,当前子视图View就会添加到父容器上,因此也就不占据屏幕空间。
3.必须在View调用layout()之后调用getHeight()和getWidth()方法获取到的View的宽高才大于0。因为只有在View调用setFrame时,才会给mLeft,mRight,mTop,mBottom赋值。

这里写图片描述

猜你喜欢

转载自blog.csdn.net/yuminfeng728/article/details/75534191
今日推荐