上篇博客我们介绍了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;}

View中有layout方法,就是对该View的摆放,该方法接收四个参数,这四个参数分别表示该View上下左右四个方向的位置。通过这四个参数来确定该View在它的父容器中的位置。首先在该方法的第12行,调用了setFrame方法将参数保存到mLeft、mTop、mRight、mBottom中(小伙伴们注意一般来说如果我们没有在xml文件中设置layoutMode属性,isLayoutModeOptical方法的返回值为false),在保存的同时调用了invalidate方法进行View的绘制。setFrame过程完成之后,接下来就是回调onLayout方法。View类中的onLayout方法只是一个空方法,里边并没有任何实现。关于onLayout方法的重点其实是在ViewGroup中。这个我们后面再述。一般来说,我们在自定义View的时候是没有必要重写layout或者onLayout方法的,只有自定义ViewGroup时才需要重写onLayout方法,这个时候再来看layout方法才会发现它的意义。说到这里小伙伴们可能会有另外一个疑问,那就是一个View的layout方法在什么时候调用,在哪里调用?其实,layout方法是在该View的父容器中调用 的,具体请看下一小节。

2.在ViewGroup中对View进行排列

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

    @Overridepublic 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 finishesmLayoutCalledWhileSuppressed = true;}}@Overrideprotected abstract void onLayout(boolean changed,int l, int t, int r, int b);

首先ViewGroup中实现了layout方法,但是小伙伴们注意,这个时候layout方法已经变成了final类型的,表示该方法不可以再被ViewGroup的子类重写,那怎么办呢?首先第7行调用了父类的layout方法,也就是第一小节我们会看到的layout方法。其次,ViewGroup中也实现了onLayout方法,但是onLayout的修饰符变为了abstract,这个表示所有继承自ViewGroup的类都需要重写该方法。实际上,所有继承自ViewGroup的容器都重写了这个方法,如果我们自定义ViewGroup时也需要重写这个方法,这里我举一个简单的例子:

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

大致的思路就是这样,我们需要在onLayout中遍历所有的View,并计算每一个View的上下左右四个点的坐标,然后调用该View的layout方法进行摆放即可,摆放完成之后再调用onDraw方法进行绘制,绘制成功之后这个View就可以显示出来了。很简单吧。这里我们以LinearLayout为例来看看LinearLayout这个容器是如何遍历子控件并摆放的。

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

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

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

这个方法倒是很简单,就是根据View的排列顺序来进行View的摆放,那么这里我就以竖直排列为例,我们来看看摆放的过程:

void layoutVertical(int left, int top, int right, int bottom) {final int paddingLeft = mPaddingLeft;int childTop;int childLeft;// Where right end of child should gofinal int width = right - left;int childRight = width - mPaddingRight;// Space available for child//容器中子控件的可用宽度(父容器总宽度减去父容器的左右内边距)int childSpace = width - paddingLeft - mPaddingRight;//获取子控件总个数final int count = getVirtualChildCount();//获取父容器的Gravityfinal 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 alreadychildTop = mPaddingTop + bottom - top - mTotalLength;break;// mTotalLength contains the padding alreadycase 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);}}
}

这个方法的整体过程可以分为两步,第一步,根据容器的android:gravity属性来计算第一个子控件的顶部起始坐标,第二步,遍历所有子View,根据子控件的android:layout_gravity属性来计算子控件左边的坐标(这个时候小伙伴们应该明白了,为什么当我的LinearLayout的排列方向设置为垂直之后,LinearLayout的子控件的layout_gravity属性设置为垂直居中会没有效果),计算出来之后,再算出子控件顶部的坐标,然后调用setChildFrame方法对子控件进行摆放。setChildFrame方法内部也是调用了layout方法来进行控件的摆放,如下:

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的一个简单的摆放过程。

以上。

转载于:https://www.cnblogs.com/lenve/p/5989995.html

View绘制详解(四),谝一谝layout过程相关推荐

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

    所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都 ...

  2. App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory

    导读 本篇文章将介绍"集合视图",App Widget 复杂布局的实现 App Widget 小部件系列其他文章链接 App Widgets 详解一 简单使用 App Widget ...

  3. python绘制单线图_CAD制图管道单线图绘制详解.ppt

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbsp计算机辅助设计 CAD制图管道单线图绘制详解.ppt89页 本文 ...

  4. java实现标准化考试系统详解(四)-----初始化操作实现

    (一)初始化操作实现 如上图所示当管理员需要更改适用工程.试题数量.考试时间时直接在文本中更改就好我们只需要每次在用户打开程序时初始化这些参数就可以 1.初始化试题模型,这里需要实现随机抽题,方法是用 ...

  5. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  6. linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  7. Android Studio 插件开发详解四:填坑

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  8. springboot 详解 (四)redis filter

    ---------------------------------------------------------------------------------------------------- ...

  9. 数据结构--图(Graph)详解(四)

    数据结构–图(Graph)详解(四) 文章目录 数据结构--图(Graph)详解(四) 一.图中几个NB的算法 1.普里姆算法(Prim算法)求最小生成树 2.克鲁斯卡尔算法(Kruskal算法)求最 ...

  10. .NET DLL 保护措施详解(四)各操作系统运行情况

    我准备了WEB应用程序及WinForm应用程序,分别在WIN SERVER 2012/2008/2003.Win7/10上实测,以下为实测结果截图: 2012 2008 2003 WIN7 WIN10 ...

最新文章

  1. LeetCode简单题之拥有最多糖果的孩‭子
  2. 鹅厂2020暑期实习第二次二面
  3. XLNet团队:赢BERT靠的并不是数据集更大,公平对决也碾压对手
  4. 微调Faster-R-CNN-InceptionV2完成高准确率安全帽检测任务
  5. 莫名其妙的SqlServer更新错误:OleDbException 必须声明标量变量
  6. ant接口自动化 junit_ant 学习(3)--结合junit形成自动化测试小框架
  7. VS中生成时“sgen.exe”已退出,代码为 1解决办法
  8. 联通研究院处长王志军:Hadoop在电信业大数据的应用
  9. vue (可读写)全局变量的 定义、任意调用、值的修改
  10. 高通driver模块编译方法
  11. [Linux] ubuntu 安装 Wireshark
  12. python 时分秒相加大于24h_在python中,将24小时加到负时间差上
  13. 必刷2022年辽宁最新消防设施操作员模拟题库及答案
  14. 故障树分析法(FTA)
  15. 计算机音乐蜗牛与黄鹂鸟,音乐教材《蜗牛与黄鹂鸟》教案
  16. 如何优化Urchin配置文件每月数据库的磁盘存储空间
  17. 计算机的简史:从数字运算到强大的现代机器
  18. 关于精简安装office2010的步骤
  19. 从进程中获取QQ号码
  20. Linux查看端口占用情况的命令

热门文章

  1. /sbin/mount.vboxsf: mounting failed with the error: Protocol error
  2. 查看dll是32还是64
  3. LINUX SHELL参数连接
  4. FireFox 64位不支持NPAPI插件,不论是32位还是64位
  5. ubuntu删除OpenCV
  6. windows VC++获取磁盘名称和序列号
  7. 关于boot.ini文件里的/noexecute=optin
  8. 【27】Python100例基础练习题6
  9. Hexo next博客添加折叠块功能添加折叠代码块
  10. SpringMVC 参数传递