Android UI树的Layout原理

来源:互联网 发布:电脑管家数据恢复收费? 编辑:程序博客网 时间:2024/06/03 17:27

在UI树的任意一个节点,当它被它的父亲layout时,这个节点的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;    boolean changed = isLayoutModeOptical(mParent) ?            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    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方法将新的坐标和宽高设置进去,在setFrame中还有一个工作就是:当size发生变化时,保存PFLAG_DRAWN位,invalidate这个View,然后恢复PFLAG_DRAWN位

然后,接下来就是onLayout方法,当前View如果不是ViewGroup,那么onLayout里面为空,什么都不做,当前View如果是ViewGroup,那么会调用每一个孩子进行layout

举FrameLayout的onLayout方法为例

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();    mForegroundBoundsChanged = true;    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;            }            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);        }    }}

总结成一句话就是:对每一个孩子,取出LayoutParams,计算left和top,然后调用child.layout方法。

0 0