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

举报
江南一点雨 发表于 2021/08/16 23:30:07 2021/08/16
【摘要】 上篇博客我们介绍了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方法,如下:


  
  1. public void layout(int l, int t, int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
  3. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }
  6. int oldL = mLeft;
  7. int oldT = mTop;
  8. int oldB = mBottom;
  9. int oldR = mRight;
  10. boolean changed = isLayoutModeOptical(mParent) ?
  11. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  12. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  13. onLayout(changed, l, t, r, b);
  14. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  15. ListenerInfo li = mListenerInfo;
  16. if (li != null && li.mOnLayoutChangeListeners != null) {
  17. ArrayList<OnLayoutChangeListener> listenersCopy =
  18. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
  19. int numListeners = listenersCopy.size();
  20. for (int i = 0; i < numListeners; ++i) {
  21. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  22. }
  23. }
  24. }
  25. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  26. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  27. }


2.在ViewGroup中对View进行排列

我们都知道,ViewGroup继承自View,而ViewGroup也重写了View中的layout和onLayout方法,而且这里还有一点点变化,我们来看看ViewGroup中的这两个方法:


  
  1. @Override
  2. public final void layout(int l, int t, int r, int b) {
  3. if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
  4. if (mTransition != null) {
  5. mTransition.layoutChange(this);
  6. }
  7. super.layout(l, t, r, b);
  8. } else {
  9. // record the fact that we noop'd it; request layout when transition finishes
  10. mLayoutCalledWhileSuppressed = true;
  11. }
  12. }
  13. @Override
  14. protected abstract void onLayout(boolean changed,
  15. int l, int t, int r, int b);


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


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

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


  
  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  3. if (mOrientation == VERTICAL) {
  4. layoutVertical(l, t, r, b);
  5. } else {
  6. layoutHorizontal(l, t, r, b);
  7. }
  8. }


  
  1. void layoutVertical(int left, int top, int right, int bottom) {
  2. final int paddingLeft = mPaddingLeft;
  3. int childTop;
  4. int childLeft;
  5. // Where right end of child should go
  6. final int width = right - left;
  7. int childRight = width - mPaddingRight;
  8. // Space available for child
  9. //容器中子控件的可用宽度(父容器总宽度减去父容器的左右内边距)
  10. int childSpace = width - paddingLeft - mPaddingRight;
  11. //获取子控件总个数
  12. final int count = getVirtualChildCount();
  13. //获取父容器的Gravity
  14. final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
  15. final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
  16. //根据底部对齐、垂直居中、顶部对齐分别来计算控件顶部的起始位置
  17. //注意mTotalLength参数是我们在上一篇博客中提到的LinearLayout测量时子View的总高度
  18. switch (majorGravity) {
  19. case Gravity.BOTTOM:
  20. // mTotalLength contains the padding already
  21. childTop = mPaddingTop + bottom - top - mTotalLength;
  22. break;
  23. // mTotalLength contains the padding already
  24. case Gravity.CENTER_VERTICAL:
  25. childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
  26. break;
  27. case Gravity.TOP:
  28. default:
  29. childTop = mPaddingTop;
  30. break;
  31. }
  32. for (int i = 0; i < count; i++) {
  33. final View child = getVirtualChildAt(i);
  34. if (child == null) {
  35. childTop += measureNullChild(i);
  36. } else if (child.getVisibility() != GONE) {
  37. //获取子View的测量宽高
  38. final int childWidth = child.getMeasuredWidth();
  39. final int childHeight = child.getMeasuredHeight();
  40. final LinearLayout.LayoutParams lp =
  41. (LinearLayout.LayoutParams) child.getLayoutParams();
  42. //从子控件的LayoutParams总获取gravity属性,但是这个gravity是指子View的android:layout_gravity属性而不是android:gravity属性
  43. int gravity = lp.gravity;
  44. if (gravity < 0) {
  45. gravity = minorGravity;
  46. }
  47. final int layoutDirection = getLayoutDirection();
  48. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
  49. //根据子控件的layout_gravity属性来计算子控件显示时的childLeft的值
  50. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  51. case Gravity.CENTER_HORIZONTAL:
  52. childLeft = paddingLeft + ((childSpace - childWidth) / 2)
  53. + lp.leftMargin - lp.rightMargin;
  54. break;
  55. case Gravity.RIGHT:
  56. childLeft = childRight - childWidth - lp.rightMargin;
  57. break;
  58. case Gravity.LEFT:
  59. default:
  60. childLeft = paddingLeft + lp.leftMargin;
  61. break;
  62. }
  63. if (hasDividerBeforeChildAt(i)) {
  64. childTop += mDividerHeight;
  65. }
  66. //子控件上面的位置再加上上边距
  67. childTop += lp.topMargin;
  68. //该方法中实际上调用了layout方法进行控件的摆放
  69. setChildFrame(child, childLeft, childTop + getLocationOffset(child),
  70. childWidth, childHeight);
  71. //对于下一个控件而言,它的起始高度是已经摆放好的View的高度之和
  72. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
  73. i += getChildrenSkipCount(child, i);
  74. }
  75. }
  76. }


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

说到这里,小伙伴们应该明白了,其实每一个控件包括容器都是在它的父容器中进行摆放的,那么这个时候小伙伴们可能会有另外一个疑问,那么我们的控件总有一个是没有父容器的,就是那个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

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

全部回复

上滑加载中

设置昵称

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

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

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