zoukankan      html  css  js  c++  java
  • 3.View绘制分析笔记之onLayout

    上一篇文章我们了解了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硬件加速等,以后会写一些笔记专门针对这些知识点做梳理。

    系列文章

    Android 视图及View绘制分析笔记之setContentView
    View绘制分析笔记之onMeasure
    View绘制分析笔记之onLayout
    View绘制分析笔记之onDraw

  • 相关阅读:
    vue-slot插槽
    js中filter函数
    js异步处理
    js变量提升
    js中的4种函数调用模式
    js方法
    Javascript中的闭包 O__O "…
    js实现瀑布流以及加载效果
    2D转换下的zoom和transform:scale的区别
    [转]JavaScript与元素间的抛物线轨迹运动
  • 原文地址:https://www.cnblogs.com/dubo-/p/6235752.html
Copyright © 2011-2022 走看看