Android View排版原理

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/wangwei708846696/article/details/80537678

由于performLayout之前是performMeasure()操作,所以不熟悉测量的小伙伴看我上一篇博客Android View 测量原理
我想了想,如果直接从ViewGroup里面的方法谈起,可能和网上很多博客一样了,但是如果只是向framework开发者分析哪些,又分析不到应用层,所以我觉得应该从performLayout()这个方法开始分析排版,因为如果在向framework层深入,那就会接触到WindowManagerService,这个过程需要掌握Binder知识,但是Binder知识很多人一时半会掌握不了,尤其是对于application开发者,不关注这些,所以从performLayout()说起。

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ...
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            ...
        }finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

这里的host是decorView,decorView对应的布局是一个FrameLayout,所以我们进入FrameLayout的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;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //判断是不是左上右下这些值有所改变,如果改变的话为true,并且在setFrame中给mLeft...mRight赋值
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    //true进入调用到onLayout方法
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ......
    }
    .....
}

继续进入onLyout方法中,我们会发现是空方法,所以我们此时想到了ViewGroup

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

继续看ViewGroup的onLayout方法,可想而知每个子类都有自己的实现,我们用LinearLayout举例

@Override
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);

LinearLayout实现方法是:

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

    // 水平可用宽度
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();//调用getChildCount()

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    //gravity属性配置的值
    switch (majorGravity) {
        //当配置bottom时候
       case Gravity.BOTTOM:
           // 看出来是已父容器总内容宽度为基准的最下面
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // 同理配置的center
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;
            // 同理配置的top
       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }

    for (int i = 0; i < count; i++) {
        //得到每一个孩子View
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();//view的宽
            final int childHeight = child.getMeasuredHeight();//view的高
            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:
                    //水平方向的话view左侧的距离
                    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;
            //设置子view的坐标
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            //加上子view的坐标继续向下排列
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            //下一个view
            i += getChildrenSkipCount(child, i);
        }
    }
}

通过上面标记的注释我们知道了对每一个子view进行排列。

同时注意几个方法

  • setFrame
      当size和position变化时,返回true。如果发生了变化,会在setFrame方法内部调用invalidate。

  • onLayout
      View中onLayout什么都没有做,在ViewGroup中,根据各自实际规则(Linear、Relative 等)对内部Views进行布局安排。

  • getMeasuredWidth与getWidth

可以调用的时机不同:getMeasuredWidth在measure后即可调用,getWidth要在layout后才可以调用。(在发生时机之前调用的话均返回0)
含义不同:getMeasuredWidth是View计算出自己的实际大小,getWidth是在布局后的大小。最简单的,在ScrollLayout中,getHeight返回屏幕内的高度,getMeasuredHeight返回屏幕内+屏幕外的总高度。

猜你喜欢

转载自blog.csdn.net/wangwei708846696/article/details/80537678