View绘制详解(四),谝一谝layout过程

举报
江南一点雨 发表于 2021/08/16 23:30:07 2021/08/16
2k+ 0 0
【摘要】 上篇博客我们介绍了View的测量过程,这只是View显示过程的第一步,第二步就是layout了,这个我们一般译作布局,其实就是在View测量完成之后根据View的大小,将其一个一个摆放在ViewGroup中的过程。OK,那我们今天就来聊聊这个过程。在本文之前我已经有过三篇博客来介绍View的绘制过程,那三篇文章有助于你理解本文: 1.View绘制详解,从LayoutInfla...

上篇博客我们介绍了View的测量过程,这只是View显示过程的第一步,第二步就是layout了,这个我们一般译作布局,其实就是在View测量完成之后根据View的大小,将其一个一个摆放在ViewGroup中的过程。OK,那我们今天就来聊聊这个过程。在本文之前我已经有过三篇博客来介绍View的绘制过程,那三篇文章有助于你理解本文:

1.View绘制详解,从LayoutInflater谈起

2.View绘制详解(二),从setContentView谈起

3.View绘制详解(三),扒一扒View的测量过程

OK,废话不多说,来看看今天的东东。本文主要包含如下几方面内容:

1.View中的layout

2.在ViewGroup中对View进行排列

3.以LinearLayout为例来看看layout过程

4.根布局的layout

1.View中的layout

要说layout过程,首先我们得先来看看View中的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;
       }
  
 


2.在ViewGroup中对View进行排列

我们都知道,ViewGroup继承自View,而ViewGroup也重写了View中的layout和onLayout方法,而且这里还有一点点变化,我们来看看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);
  
 


      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
      //获取容器中的控件(假设只有一个)
       View view = getChildAt(0);
      //设置该View上下左右四个点的坐标,对View进行摆放
       view.layout(0, 0, 100, 100);
       }
  
 


3.以LinearLayout为例来看看layout过程

这里我们以LinearLayout为例,来看看View到底是如何摆放在一个容器中的。因为我们说过,凡是继承自ViewGroup的类都是不能重写layout方法的,但是同时又必须重写onLayout方法,所以这里我们就先来看看LinearLayout的onLayout方法:


      @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;
      // Space available for child
      //容器中子控件的可用宽度(父容器总宽度减去父容器的左右内边距)
      int childSpace = width - paddingLeft - mPaddingRight;
      //获取子控件总个数
      final int count = getVirtualChildCount();
      //获取父容器的Gravity
      final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
      final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
      //根据底部对齐、垂直居中、顶部对齐分别来计算控件顶部的起始位置
      //注意mTotalLength参数是我们在上一篇博客中提到的LinearLayout测量时子View的总高度
      switch (majorGravity) {
      case Gravity.BOTTOM:
      // mTotalLength contains the padding already
       childTop = mPaddingTop + bottom - top - mTotalLength;
      break;
      // mTotalLength contains the padding already
      case Gravity.CENTER_VERTICAL:
       childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
      break;
      case Gravity.TOP:
      default:
       childTop = mPaddingTop;
      break;
       }
      for (int i = 0; i < count; i++) {
      final View child = getVirtualChildAt(i);
      if (child == null) {
       childTop += measureNullChild(i);
       } else if (child.getVisibility() != GONE) {
      //获取子View的测量宽高
      final int childWidth = child.getMeasuredWidth();
      final int childHeight = child.getMeasuredHeight();
      final LinearLayout.LayoutParams lp =
       (LinearLayout.LayoutParams) child.getLayoutParams();
      //从子控件的LayoutParams总获取gravity属性,但是这个gravity是指子View的android:layout_gravity属性而不是android:gravity属性
      int gravity = lp.gravity;
      if (gravity < 0) {
       gravity = minorGravity;
       }
      final int layoutDirection = getLayoutDirection();
      final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
      //根据子控件的layout_gravity属性来计算子控件显示时的childLeft的值
      switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
      case Gravity.CENTER_HORIZONTAL:
       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;
      //该方法中实际上调用了layout方法进行控件的摆放
       setChildFrame(child, childLeft, childTop + getLocationOffset(child),
       childWidth, childHeight);
      //对于下一个控件而言,它的起始高度是已经摆放好的View的高度之和
       childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
       i += getChildrenSkipCount(child, i);
       }
       }
      }
  
 


      private void setChildFrame(View child, int left, int top, int width, int height) {
       child.layout(left, top, left + width, top + height);
       }
  
 

说到这里,小伙伴们应该明白了,其实每一个控件包括容器都是在它的父容器中进行摆放的,那么这个时候小伙伴们可能会有另外一个疑问,那么我们的控件总有一个是没有父容器的,就是那个DecorView,那么DecorView又是在哪里进行摆放的呢?请看下文。

4.根布局的layout

OK,控件摆放还剩一个小问题,就是DecorView是在哪里摆放?其实和我们之前说的DecorView是在哪里进行测量是同一个问题,对于这个问题,我们还是得回到ViewRootImpl中去寻找答案。我们都知道View绘制过程的启动是从performTraversals方法开始的,在这个方法中系统首先进行了View的测量,然后调用了performLayout方法进行View的摆放,performLayout中又调用了layout方法来进行控件的摆放,整个流程基本就是这样。这里的方法略长,我就不贴出来了,有兴趣的小伙伴们可以自行查看。

OK,这就是layout的一个简单的摆放过程。

以上。

文章来源: wangsong.blog.csdn.net,作者:_江南一点雨,版权归原作者所有,如需转载,请联系作者。

原文链接:wangsong.blog.csdn.net/article/details/52734970

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。