前言

上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了。

1.View的layout流程

先来看看View的layout()方法:

  1. public void layout(int l, int t, int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != ) {
  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 = ; 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. }

传进来里面的四个参数分别是View的四个点的坐标,它的坐标不是相对屏幕的原点,而且相对于它的父布局来说的。 l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离;
r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。来看看setFrame()方法里写了什么:

  1. protected boolean setFrame(int left, int top, int right, int bottom) {
  2. boolean changed = false;
  3. if (DBG) {
  4. Log.d("View", this + " View.setFrame(" + left + "," + top + ","
  5. + right + "," + bottom + ")");
  6. }
  7. if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
  8. changed = true;
  9. // Remember our drawn bit
  10. int drawn = mPrivateFlags & PFLAG_DRAWN;
  11. int oldWidth = mRight - mLeft;
  12. int oldHeight = mBottom - mTop;
  13. int newWidth = right - left;
  14. int newHeight = bottom - top;
  15. boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
  16. // Invalidate our old position
  17. invalidate(sizeChanged);
  18. mLeft = left;
  19. mTop = top;
  20. mRight = right;
  21. mBottom = bottom;
  22. mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
  23. ...省略
  24. }
  25. return changed;
  26. }

在setFrame()方法里主要是用来设置View的四个顶点的值,也就是mLeft 、mTop、mRight和 mBottom的值。在调用setFrame()方法后,调用onLayout()方法:

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

onLayout()方法没有去做什么,这个和onMeasure()方法类似,确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现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. }

layoutVertical做了什么呢?

  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. int childSpace = width - paddingLeft - mPaddingRight;
  10. final int count = getVirtualChildCount();
  11. final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
  12. final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
  13. switch (majorGravity) {
  14. case Gravity.BOTTOM:
  15. // mTotalLength contains the padding already
  16. childTop = mPaddingTop + bottom - top - mTotalLength;
  17. break;
  18. // mTotalLength contains the padding already
  19. case Gravity.CENTER_VERTICAL:
  20. childTop = mPaddingTop + (bottom - top - mTotalLength) / ;
  21. break;
  22. case Gravity.TOP:
  23. default:
  24. childTop = mPaddingTop;
  25. break;
  26. }
  27. for (int i = ; i < count; i++) {
  28. final View child = getVirtualChildAt(i);
  29. if (child == null) {
  30. childTop += measureNullChild(i);
  31. } else if (child.getVisibility() != GONE) {
  32. final int childWidth = child.getMeasuredWidth();
  33. final int childHeight = child.getMeasuredHeight();
  34. final LinearLayout.LayoutParams lp =
  35. (LinearLayout.LayoutParams) child.getLayoutParams();
  36. int gravity = lp.gravity;
  37. if (gravity < ) {
  38. gravity = minorGravity;
  39. }
  40. final int layoutDirection = getLayoutDirection();
  41. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
  42. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  43. case Gravity.CENTER_HORIZONTAL:
  44. childLeft = paddingLeft + ((childSpace - childWidth) / )
  45. + lp.leftMargin - lp.rightMargin;
  46. break;
  47. case Gravity.RIGHT:
  48. childLeft = childRight - childWidth - lp.rightMargin;
  49. break;
  50. case Gravity.LEFT:
  51. default:
  52. childLeft = paddingLeft + lp.leftMargin;
  53. break;
  54. }
  55. if (hasDividerBeforeChildAt(i)) {
  56. childTop += mDividerHeight;
  57. }
  58. childTop += lp.topMargin;
  59. setChildFrame(child, childLeft, childTop + getLocationOffset(child),
  60. childWidth, childHeight);
  61. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
  62. i += getChildrenSkipCount(child, i);
  63. }
  64. }
  65. }

这个方法会遍历子元素并调用setChildFrame()方法:

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

在setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值是逐渐增大的,这是为了在垂直方向,子元素是一个接一个排列的而不是重叠的。

2.View的draw流程

View的draw流程很简单,先来看看View的draw()方法:

  1. public void draw(Canvas canvas) {
  2. final int privateFlags = mPrivateFlags;
  3. final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
  4. (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
  5. mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
  6. // Step 1, draw the background, if needed
  7. int saveCount;
  8. if (!dirtyOpaque) {
  9. drawBackground(canvas);
  10. }
  11. ...
  12. // Step 2, save the canvas' layers
  13. int paddingLeft = mPaddingLeft;
  14. final boolean offsetRequired = isPaddingOffsetRequired();
  15. if (offsetRequired) {
  16. paddingLeft += getLeftPaddingOffset();
  17. }
  18. ...
  19. // Step 3, draw the content
  20. if (!dirtyOpaque) onDraw(canvas);
  21. // Step 4, draw the children
  22. dispatchDraw(canvas);
  23. ...
  24. // Step 5, draw the fade effect and restore layers
  25. final Paint p = scrollabilityCache.paint;
  26. final Matrix matrix = scrollabilityCache.matrix;
  27. final Shader fade = scrollabilityCache.shader;
  28. ...
  29. // Step 6, draw decorations (scrollbars)
  30. onDrawScrollBars(canvas);
  31. if (mOverlay != null && !mOverlay.isEmpty()) {
  32. mOverlay.getOverlayView().dispatchDraw(canvas);
  33. }
  34. }

从源码的注释我们看到draw流程有六个步骤,其中第2步和第5步可以跳过:

  1. 如果有设置背景,则绘制背景
  2. 保存canvas层
  3. 绘制自身内容
  4. 如果有子元素则绘制子元素
  5. 绘制效果
  6. 绘制装饰品(scrollbars)

好了,关于View的工作流程就讲到这里了,接下来会讲到自定义View。

Android View体系(八)从源码解析View的layout和draw流程的更多相关文章

  1. Android IntentService使用介绍以及源码解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.IntentService概述及使用举例 IntentService内部实现机制用到了HandlerThread,如果对HandlerThrea ...

  2. Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer

    Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 目录 Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 0x00 ...

  3. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  4. 【Android应用开发】EasyDialog 源码解析

    示例源码下载 : http://download.csdn.net/detail/han1202012/9115227 EasyDialog 简介 : -- 作用 : 用于在界面进行一些介绍, 说明; ...

  5. Android Handler机制(四)---Handler源码解析

    Handler的主要用途有两个:(1).在将来的某个时刻执行消息或一个runnable,(2)把消息发送到消息队列. 主要依靠post(Runnable).postAtTime(Runnable, l ...

  6. Android Handler机制(三)----Looper源码解析

    一.Looper Looper对象,顾名思义,直译过来就是循环的意思,从MessageQueue中不断取出message. Class used to run a message loop for a ...

  7. Android Handler机制(二)---MessageQueue源码解析

    MessageQueue 1.变量 private final boolean mQuitAllowed;//表示MessageQueue是否允许退出 @SuppressWarnings(" ...

  8. tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解

    TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...

  9. Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程

    一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...

随机推荐

  1. Qt之实现360安全卫士主界面代码开源

    匆匆一年又过去了,总结去年一年的节奏就是忙爆了:生活忙.工作忙,值得庆幸的是没有瞎忙:今天打开博客园查看我的博客,才发现几乎差不多一年时间没写博客了:博客文章就是记忆,就是曾经努力过的见证,感谢博客园 ...

  2. Scala + IntelliJ IDEA

    学习路上的新起点:大数据Scala + Spark +(HDFS + HBase),本文主要介绍下Scala的基本语法和用法吧.最后再简单介绍一种Java开发工具IntelliJ IDEA的使用. S ...

  3. 简介 - RESTful

    RESTful REST(Representational State Transfer,表现层状态转化),可以简单理解为"资源在网络中以某种表现形式进行状态转移" Resourc ...

  4. Testing - 软件测试知识梳理 - 测试用例

    测试用例 是指对一项特定的软件产品进行测试任务的描述,体现测试方案.方法.技术和策略. 内容包括测试目标.测试环境.输入数据.测试步骤.预期结果.测试脚本等,并形成文档. 每个具体测试用例都将包括下列 ...

  5. javascript 实现数据结构 - 队列

    队列是遵循FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序的项.队列在尾部添加新元素,并从顶部移除元素.最新添加的元素必须排在队列的末尾. 1.构造函数构建队 ...

  6. python之线程(threading)

    线程是属于进程的,一个进程可能包含多个线程 至于线程和进程在使用时哪个更好,只能看使用的场景了 话不多说,看下线程模块(threading)的使用方法: #导入模块 import threading, ...

  7. ubuntu18.04 运行时提示缺少libstdc++.so.6

    解决方法:输入命令 sudo apt- 提示:ubuntu默认软件包管理器不是yum,而是dpkg,安装软件时用apt-get PS:在ubuntu下最好不要去装yum,不然可能会出现一些奇怪的问题

  8. Ubuntu 16.04下如何安装VMware-Workstation

    一.下载 下载地址:https://my.vmware.com/cn/group/vmware/details?downloadGroup=WKST-1411-LX&productId=686 ...

  9. QC内部分享ppt

    Quality Center是一个基于Web的测试管理工具,可以组织和管理应用程序测试流程的所有阶段,包括制定测试需求.计划测试.执行测试和跟踪缺陷.此外,通过Quality Center还可以创建报 ...

  10. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十六):容器部署项目

    容器部署项目 这一章我们引入docker,采用docker容器的方式部署我们的项目. 首先需要有一个linux环境,并且安装 java 和 maven 以及 docker 环境,这个教程多如牛毛,不再 ...