概述

Android中View框架的工作机制中,主要有三个过程:

1、View树的测量(measure) Android View框架的measure机制

2、View树的布局(layout)Android View框架的layout机制

3、View树的绘制(draw)Android View框架的draw机制

View框架的工作流程为:测量每个View大小(measure)-->把每个View放置到相应的位置(layout)-->绘制每个View(draw)。

         本文主要讲述三大流程中的layout过程。不清楚measure过程的,可以参考这篇文章 Android View框架的measure机制

带着问题来思考整个layout过程。

1、系统为什么要有layout过程?

View框架在经过第一步的measure过程后,成功计算了每一个View的尺寸。但是要成功的把View绘制到屏幕上,只有View的尺寸还不行,还需要准确的知道该View应该被绘制到什么位置。除此之外,对一个ViewGroup而言,还需要根据自己特定的layout规则,来正确的计算出子View的绘制位置,已达到正确的layout目的。这也就是layout过程的职责。

该位置是View相对于父布局坐标系的相对位置,而不是以屏幕坐标系为准的绝对位置。这样更容易保持树型结构的递归性和内部自治性。而View的位置,可以无限大,超出当前ViewGroup的可视范围,这也是通过改变View位置而实现滑动效果的原理。

2、layout过程都干了点什么事?

由于View是以树结构进行存储,所以典型的数据操作就是递归操作,所以,View框架中,采用了内部自治的layout过程。

每个叶子节点根据父节点传递过来的位置信息,设置自己的位置数据,每个非叶子节点,除了负责根据父节点传递过来的位置信息,设置自己的位置数据外(如果有父节点的话),还需要根据自己内部的layout规则(比如垂直排布等),计算出每一个子节点的位置信息,然后向子节点传递layout过程。

对于ViewGroup,除了根据自己的parent传递的位置信息,来设置自己的位置之外,还需要根据自己的layout规则,为每一个子View计算出准确的位置(相对于子View的父布局的位置)。

对于View,根据自己的parent传递的位置信息,来设置自己的位置。

View对象的位置信息,在内部是以4个成员变量的保存的,分别是mLeft、mRight、mTop、mBottom。他们的含义如图所示。

源代码分析

在View的源代码中,提取到了下面一些关于layout过程的信息。

我们知道,整棵View树的根节点是DecorView,它是一个FrameLayout,所以它是一个ViewGroup,所以整棵View树的测量是从一个ViewGroup对象的layout方法开始的。

View:

1、layout

/**

分配一个位置信息到一个View上面,每个parent会调用children的layout方法来设置children的位置。最好不要覆写该方法,有children的viewGroup,应该覆写onLayout方法

*/

public void layout(int l, int t, int r, int b) ;

源代码:
这里不给出,如果有兴趣,自行查阅SDK。

伪代码:

  1. public void layout(int l, int t, int r, int b) {
  2. if (根据一些flag,发现需要进一步measure) {
  3. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  4. }
  5. //暂存旧的位置信息
  6. int oldL = mLeft;
  7. int oldT = mTop;
  8. int oldB = mBottom;
  9. int oldR = mRight;
  10. //设置新的位置信息
  11. mLeft = l;
  12. mTop = t;
  13. mBottom = b;
  14. mRight = r;
  15.  
  16. if (layout改变了 || 需要layout) {
  17. onLayout(changed, l, t, r, b);
  18.  
  19. //回调layoutChange事件
  20. for (遍历监听对象) {
  21. listener.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  22. }
  23. }
  24.  
  25. 标记为已经执行过layout;
  26. }

2、onLayout

/** 根据布局规则,计算每一个子View的位置,View类默认是空实现。 所以这里没有源代码*/
protected void
onLayout(boolean changed, int left, int top, int right, int bottom);

ViewGroup:

ViewGroup中,只需要覆写onLayout方法,来计算出每一个子View的位置,并且把layout流程传递给子View。

源代码:

ViewGroup没有实现,具体可以参考LinearLayout和RelativeLayout的onLayout方法。虽然各个具体实现都很复杂,但是基本流程是一样的,可以参考下面的伪代码。

伪代码:

  1. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  2. for (遍历子View) {
  3. /**
  4. 根据如下数据计算。
  5. 1、自己当前布局规则。比如垂直排放或者水平排放。
  6. 2、子View的测量尺寸。
  7. 3、子View在所有子View中的位置。比如位置索引,第一个或者第二个等。
  8. */
  9. 计算每一个子View的位置信息;
  10.  
  11. child.layout(上面计算出来的位置信息);
  12. }
  13. }

结论

一般来说,自定义View,如果该View不包含子View,类似于TextView这种的,是不需要覆写onLayout方法的。而含有子View的,比如LinearLayout这种,就需要根据自己的布局规则,来计算每一个子View的位置。

动手操作

下面我们自己写一个自定义的ViewGroup,让它内部的每一个子View都垂直排布,并且让每一个子View的左边界都距离上一个子View的左边界一定的距离,大概看起来如下图所示:

实际运行效果如下:

代码如下:

  1. public class VerticalOffsetLayout extends ViewGroup {
  2.  
  3. private static final int OFFSET = 100;
  4.  
  5. public VerticalOffsetLayout(Context context) {
  6. super(context);
  7. }
  8.  
  9. public VerticalOffsetLayout(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. }
  12.  
  13. public VerticalOffsetLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  14. super(context, attrs, defStyleAttr);
  15. }
  16.  
  17. @Override
  18. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  19. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  20.  
  21. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  22. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  23. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  24. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  25.  
  26. int width = 0;
  27. int height = 0;
  28.  
  29. int childCount = getChildCount();
  30. for (int i = 0; i < childCount; i++) {
  31. View child = getChildAt(i);
  32. ViewGroup.LayoutParams lp = child.getLayoutParams();
  33. int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
  34. int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
  35. child.measure(childWidthSpec, childHeightSpec);
  36. }
  37.  
  38. switch (widthMode) {
  39. case MeasureSpec.EXACTLY:
  40. width = widthSize;
  41. break;
  42. case MeasureSpec.AT_MOST:
  43. case MeasureSpec.UNSPECIFIED:
  44. for (int i = 0; i < childCount; i++) {
  45. View child = getChildAt(i);
  46. int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
  47. width = Math.max(width, widthAddOffset);
  48. }
  49. break;
  50. default:
  51. break;
  52.  
  53. }
  54.  
  55. switch (heightMode) {
  56. case MeasureSpec.EXACTLY:
  57. height = heightSize;
  58. break;
  59. case MeasureSpec.AT_MOST:
  60. case MeasureSpec.UNSPECIFIED:
  61. for (int i = 0; i < childCount; i++) {
  62. View child = getChildAt(i);
  63. height = height + child.getMeasuredHeight();
  64. }
  65. break;
  66. default:
  67. break;
  68.  
  69. }
  70.  
  71. setMeasuredDimension(width, height);
  72. }
  73.  
  74. @Override
  75. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  76. int left = 0;
  77. int right = 0;
  78. int top = 0;
  79. int bottom = 0;
  80.  
  81. int childCount = getChildCount();
  82.  
  83. for (int i = 0; i < childCount; i++) {
  84. View child = getChildAt(i);
  85. left = i * OFFSET;
  86. right = left + child.getMeasuredWidth();
  87. bottom = top + child.getMeasuredHeight();
  88.  
  89. child.layout(left, top, right, bottom);
  90.  
  91. top += child.getMeasuredHeight();
  92. }
  93. }
  94. }

Android View框架的layout机制的更多相关文章

  1. Android View框架的measure机制

    概述 Android中View框架的工作机制中,主要有三个过程: 1.View树的測量(measure)Android View框架的measure机制 2.View树的布局(layout) Andr ...

  2. Android View框架的draw机制

    概述 Android中View框架的工作机制中,主要有三个过程: 1.View树的测量(measure) Android View框架的measure机制 2.View树的布局(layout)Andr ...

  3. Android View框架总结(八)ViewGroup事件分发机制

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52298780 上篇分析了View的事件分发流程,留了一个问题:如果上 ...

  4. Android View的事件分发机制探索

    概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解f ...

  5. Android View框架总结(四)View布局流程之Measure

    View树的measure流程 View的measures时序图 View布局流程之measure measure过程 View的measure过程 ViewGroup的measure过程 Frame ...

  6. Android View框架总结(七)View事件分发机制

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52282833 View布局告一段落,从本篇开始View事件相关分析, ...

  7. Android View框架总结(五)View布局流程之Layout

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52216195 View树的Layout流程 View的Layout时序图 View布局 ...

  8. Android view 的事件分发机制

    1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * ...

  9. Android View框架总结(二)View焦点

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52263256 前言:View框架写到第六篇,发现前面第二篇竟然没有, ...

随机推荐

  1. 排查 Kubernetes HPA 通过 Prometheus 获取不到 http_requests 指标的问题

    部署好了 kube-prometheus 与 k8s-prometheus-adapter (详见之前的博文 k8s 安装 prometheus 过程记录),使用下面的配置文件部署 HPA(Horiz ...

  2. python yml 文件处理

    安装 pip install pyyaml import yaml import io s = {'host': {'ip00': '10.0.0.1', 'ip01': {'one': '10.0. ...

  3. linux系统的启动流程梳理

    1. 不同版本的linux系统的启动流程 1.1 centos6.x系统的启动流程 其详细启动步骤如下: 1)开机,BIOS自检,检查各个硬件是否正常 2)读取硬盘MBR信息,引导系统启动 3)加载g ...

  4. [PowerShell]Windows服务开启、重启、关闭

    # 获取服务信息 PS C:\Users\Administrator> Get-Service win* Status Name DisplayName ------ ---- -------- ...

  5. 杭电-------2048不容易系列之(4)考新郎(C语言)

    /* 思路:有n位新郎,但是又m位新郎会找错,那么有n-m位新郎会找对,而找对的n-m位新郎的找发就是在 n位新郎中随机找n-m位有多少种排列组合公式有n!/(m!*(n-m!)),而另外找错的新郎则 ...

  6. Rust学习--变量

    0x0 每种编程语言都有变量的概念,我们可以把变量理解为最简单的存储方式,它是编码过程中是必不可少的. Rust的变量很有特色.变量不可变的特性让人想起了Erlang.以及后面的模式匹配,我觉得作者应 ...

  7. MSFVENOM SHELLCODE生成备忘录

    MSFVENOM SHELLCODE生成 通用Shellcode msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHO ...

  8. PWA(Progressive web apps),渐进式 Web 应用

    学习博客:https://www.jianshu.com/p/098af61bbe04 学习博客:https://www.zhihu.com/question/59108831 官方文档:https: ...

  9. redis两种持久化方式RDB和AOF

    目录 前言 1. Redis 数据库结构 2. RDB 持久化 2.1. RDB 的创建和载入 2.1.1. 手动触发保存 SAVE 命令 BGSAVE 命令 SAVE 和 BGSAVE 的比较 2. ...

  10. Vue整合d3.v5.js制作--折线图(line)

    先上效果图(x轴固定为时间轴): 图中出现的悬浮框是鼠标悬停效果 1.环境说明: vue版本:"vue": "^2.5.2" d3版本:"d3&quo ...