Android 基本功-View 的工作流程(三)

测量完之后的下一步就要开始布局摆放了。这个拜访跟具体容器类型有很大关系,例如 LinearLayout 和 RelativeLayout 的布局规则完全不一样,所以要结合具体容器考虑。

先从 ViewRootImpl 的 performLayout() 方法看起,

//ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    //依然从 DecorView 开始
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    //接下去的像是在布局过程中的二次执行工作流程,暂时先不考虑
    //先继续看 DecorView 的 layout
}

DecorView 的 layout 过程估计和 measure 过程差不多,也是先调用父类 View 的方法 layout(),然后再调用自己和父类 FrameLayout 的方法,

//先看 View 的 layout
public void layout(int l, int t, int r, int b) {
    //把旧的位置信息缓存起来,到时候给回调响应用
    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);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //和 onMeasure 很相似
        onLayout(changed, l, t, r, b);
        //这里是看看有没有布局更新的监听器,有的话,要做监听回调
        //可见如果 View 实现了 OnLayoutChangeListener 就会在这个时候回调
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList listenersCopy = (ArrayList)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);
            }
        }
    }
}
//DecorView 的 onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    //下面这些代码我理解的是,经过 FrameLayout 的 onLayout 之后
    //布局位置基本已经定了,只是再修正一些有效区域,所以重点在 super.onLayout 上
    getOutsets(mOutsets);
    if (mOutsets.left > 0) {
        offsetLeftAndRight(-mOutsets.left);
    }
    if (mOutsets.top > 0) {
        offsetTopAndBottom(-mOutsets.top);
    }
    if (mApplyFloatingVerticalInsets) {
        offsetTopAndBottom(mFloatingInsets.top);
    }
    if (mApplyFloatingHorizontalInsets) {
        offsetLeftAndRight(mFloatingInsets.left);
    }
    updateElevation();
    mAllowUpdateElevation = true;
    if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
        getViewRootImpl().requestInvalidateRootRenderNode();
    }
}

FrameLayout 的 onLayout() 方法估摸着也会涉及到遍历子 View,

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();
    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;
            //简单看就是对于不是 GONE 的 View 会根据尺寸和左上角坐标进行布局
            //这里的核心工作就是确定左上角坐标点,
            //View 的尺寸已经在测量的时候测量好了
            //这里也知道了一点,对于 GONE 和 INVISIBLE 的效果,虽说有可能看去一样,但代码执行上是不一样的,所以能 GONE 的地方尽量 GONE
            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;
            }
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

经过上面过程的分析,可见容器类的 View 的 layout 步骤是必须的,并且其实系统提供的一些控件,例如 TextView(找了一些常用控件,发现基本上父类都是 TextView ......)也自己实现了 onLayout() 方法,不过也有没实现的,例如 ImageView。看来对具体控件来说 onLayout 的实现可以不需要。

你可能感兴趣的:(Android 基本功-View 的工作流程(三))