Android 自定义控件基础:layout 过程

layout 过程作用是 ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout 中遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法 确定View 本身的位置,而 onLayout方法 则会确定所有子元素的位置。

ViewRootImpl

View 的 layout 起始点也是从 ViewRootImpl 开始的,ViewRootImpl 的 performLayout 方法会调用 DecorView 的 layout 方法来启动 layout 流程。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
   ....
   mInLayout = true;
   // 将全局变量mView(DecorView)赋值给host
   final View host = mView;
   try {
            // host.getMeasuredWidth() 屏幕宽、host.getMeasuredHeight() 屏幕高
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mInLayout = false;
            .....
   }finally {
      Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   }
   mInLayout = false;
}

从上面performLayout() 方法可以看出,host.layout() 方法也就是View.layout 方法,layout()方法的四个参数分别是当前View的左(left),上(top),右(right),下(bottom)。

View

layout 是 View 类中的方法,传入的四个参数即我们熟知的 left、top、right、bottom,这四个值都是 View 相对父容器 ViewGroup 的坐标值。

// View 源码

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;
        }
        //保存上一次View的四个点的位置
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        // isLayoutModeOptical 判断mParent是否ViewGroup 并且布局是否有变换
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
       //在第一次或是位置改变时changed=true 条件成立视图View重新布局         
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

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

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        .....
}

public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;
        Insets childInsets = getOpticalInsets();
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
 }
 
protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        ....
        
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            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 our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            .....
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        return changed;
    }

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

public final int getWidth() {
        return mRight - mLeft;
}

public final int getHeight() {
        return mBottom - mTop;
}

从上面源码看, 在View 的layout() 方法中通过setFrame 方法来设定View 的四个顶点位置,即初始化mLeft、mTop、mBottom、mRight四个值,View 的四个顶点一旦确定,那么View 在父容器中的位置也就确定了;如果View 类型是ViewGroup ,接着会调用onLayout方法,确定子元素在父容器中的位置,onLayout 的具体实现同样和具体的布局有关,所以View和ViewGroup均 没有真正实现onLayout方法。

ViewGroup

layout 方法又会调用自身的 onLayout 方法。onLayout 方法在 View 类中是空实现,大部分情况下 View 都无需重写该方法。而 ViewGroup 又将其改为了抽象方法,即每个 ViewGroup 子类都需要通过实现该方法来管理自己的所有 childView 的摆放位置,FrameLayout 和 LinearLayout 等容器类就通过实现该方法来实现不同的布局效果

// ViewGroup 源码

@Override
public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
}

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

从上面源码来看,ViewGroup 的layout方法最终还是调用了View 的layout 方法。onLayout 方法 是抽象方法,由ViewGroup 子类去实现该方法。下面可以查看下,ViewGroup 子类的FrameLayout 是如何实现的。

FrameLayout
// FrameLayout 源码

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

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
       //遍历所有FrameLayout下的子View
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //判断当前子View的可见度。不过是GONE那么就不进行布局
            if (child.getVisibility() != GONE) {
                 //获取子View的getLayoutParams
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;
               //根据子View的LayoutParams来获取gravity的设置
                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;
             //下面都是根据gravity属性的设置来决定如何设置子View四个点的值
                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;
                }
               //设置完成子View的四个点的值传入子View的layout方法开始布局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

从上面的代码来看,FrameLayout直接调用了layoutChildren()方法,在这个方法中完成每个子View的布局。这个方法中通过对对齐方式和Margin的计算,来获得子View四个点的位置,最后调用child.layout()方法,如果是View就会走上面View的布局;如果是ViewGrouop那么就和上面FrameLayout的布局逻辑一致。 (这里说的逻辑一致是因为直接继承ViewGroup的容器都会根据自己的特点重写onLayout()方法。比如LinearLayout和FrameLayout的布局方式是不同。但是最后都是会调用child.layout()方法,也就是逻辑都是一样的)
在这里插入图片描述

补充
类型作用赋值时机使用场景
getMeasuredWidth() / getMeasuredHeight()获得 View测量的宽 / 高measure过程在layout()过程中获取宽/高
getWidth() / getHeight()获得View最终的宽 / 高layout过程在除layout()以外得放中获取宽/高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值