继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

View绘制分析笔记之onLayout

皈依舞
关注TA
已关注
手记 99
粉丝 13
获赞 37

上一篇文章我们了解了View的onMeasure,那么今天我们继续来学习Android 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(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        //此处省略的代码是在layout的过程中,重复的requestLayout,需要做的处理。
        //具体的处理方案是重新measure,layout。
        ...
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

这个方法主要的作用就是调用了host.layout,并把已经测绘好的宽高传计算成上下左右递过去,host就是decorView。

View#layout

public void layout(int l, int t, int r, int b) {
    //根据mPrivateFlags3标记位状态判断,如果需要,则重新measure。
    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;
    
    //检查是位置有变化,并setFrame
    //setFrame分析见下文
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
            
    //如果位置有变化或者PFLAG_LAYOUT_REQUIRED标记位为on,则进行onLayout
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        //把PFLAG_LAYOUT_REQUIRED标记位置为off
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        
        //进行onLayoutChange回调
        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);
            }
        }
    }
    
    //将PFLAG_FORCE_LAYOUT标记置为off,将PFLAG3_IS_LAID_OUT置为on
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

View#setFrame

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

    if (DBG) {
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }
    
    //如果上下左右任意一项有改动,则继续往下进行,否则直接返回false
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        //记录PFLAG_DRAWN位状态,最后复原的时候需要
        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方法将在另一篇文章中详细展开。
        invalidate(sizeChanged);

        //设置该View的上下左右,也是setFrame的核心功能
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        //PFLAG_HAS_BOUNDS位置为on
        mPrivateFlags |= PFLAG_HAS_BOUNDS;

        //如果尺寸有改变,调用onSizeChange并且调用rebuildOutline
        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();
        }

        // 把PFLAG_DRAWN设置为原有数值。(invalidate过程中会将其设为off)
        mPrivateFlags |= drawn;

        mBackgroundSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }
        
        //Android无障碍辅助通知
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

FrameLayout#onLayout

如果是View的话,执行完layout方法,那么他已经布局完成,不过如果是ViewGroup,那么它需要对它的子View进行处理。onLayout主要的作用就是调用layoutChildren,对子View进行布局,所以这里着重介绍layoutChildren。

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

    //计算parent的上下左右
    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;
            }

            //获取layout默认方向,通常是从左到右,在某些特定语言的情况下是从右到左
            final int layoutDirection = getLayoutDirection();
            //通过刚才的方向值,计算出绝对的横向位置属性
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            //计算竖向位置属性
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            //通过位置属性,计算子View的left和right
            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;
            }

            //通过位置属性,计算子View的top和bottom
            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的layout方法
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

时序图

图为View layout 时序图图为View layout 时序图

小结

到这里就介绍完了View绘制的layout方法。比起measure,layout可是简单多了。不过这里还预留了一些坑,没有交代清楚,比如invalidate,还有RenderNode硬件加速等,以后会写一些笔记专门针对这些知识点做梳理。

原文链接:http://www.apkbus.com/blog-719059-63112.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP