配合Activity 从启动到布局绘制的简单分析 阅读

基本概念介绍

  • Activity:一个 Activity 是一个应用程序组件,提供一个屏幕,用户可以用来交互。
  • View:所有视图控件的基类
  • ViewGroup:View 的子类,是容器类控件,内部用于放置子View
  • Window:概况了 Android 窗口的基本属性和基本功能(抽象类)
  • PhoneWindow:Window 的实现类
  • DecorView: 界面的 根 View,PhoneWindow 的内部类,FrameLayout 的子类
  • ViewRootImpl:官方定义是 The top of a view hierarchy,implementing the needed protocol between View and the WindowManager. 在 View 层级中的顶层,可以认为是 View 树的根(注意 ViewRootImpl 不是 View,只是根,DecorView 是根 View,属于 View)用于串联 Window 和 View视图
  • WindowManager:是用来管理窗口的(Window)它的实现对象是 WindowManagerImpl,内部的大部分方法真正的实现是 WindowMangerGlobal
  • WindowManagerService:简称 WMS,作用是管理所有应用程序中的窗口



Activity 启动过程简单介绍

Activity 设置页面布局的过程

  1. 在 ActivityThread 主线程中 newActivity 生成一个 Activity

  2. 然后调用 Activity 的attach 方法,attach 方法中生成 PhoneWindow 对象

  3. setContentView 中初始化 DecorView (ViewGroup 的子类)其实真正的执行是在 PhoneWindow 中的 setContentView

  4. 在 LayoutInflater 中对布局文件进行 xml 解析获取对象的数据

  5. 根据解析出的数据执行 View 的构造函数进行 View 的创建。

    上面内容是在 onCreate() 中执行完成的

  6. 然后在 onResume 执行完成后调用View的绘制

详细的说明看:Activity 从启动到布局绘制的简单分析

View 的绘制

View 的绘制流程可以分成三步:测量、布局、绘制

分别对应了:onMeasure() onLayout() onDraw 当然这个过程中也会调用许多其他的方法,都是作为辅助,大的流程就这三步。其中这三步内部的执行都是呈现树状结构,从根 View 开始循环递进,直到所有子 View 全部执行完毕。

测量 onMeasure

onMeasure(int widthMeasureSpec,int heightMeasureSpec) 这个方法对于单控件来说,只是测量他自己,但是对于 ViewGroup 来说还要正确的给它的子控件传入期望的测量数值。然后根据所有子控件的大小和 onMeasure 中的参数来设置自己本身的大小。

关于 MeasureSpec 就不多解释了,这里只说一下内部的三种模式

  • MeasureSpec.EXACTLY 意思是精确大小,当你想给这个 View 一个精确的大小的时候就是用这个参数,比如布局中指定了大小是 10 dp 或者 match_parent 都是属于这种类型的
  • MeasureSpec.AT_MOST 意思是父布局会给一个最大的值,大小不能超过这个值(就是定义的这种标准,当然你不按照这个标准,在自己写 onMeasure() 的时候,明明父布局给的类型是 AT_MOST 你还要超过,那也没事,只是布局的样子可能就会有问题了)。对应 wrap_content 模式
  • MeasureSpec.UNSPECIFIED 意思没有指定尺寸,这种情况不常见,一般都是父控件是 AdapterView 通过 measure 方法传入模式。

关于 ViewGroup 的自定义,onMeasure() 方法内部需要实现什么?

首先我们需要给这个控件设置正确的期望大小 setMeasuredDimension(width,height) 要想正确的获取 width 和 height 还需要根据 onMeasure(int widthMeasureSpec,int heightMeasureSpec) 中的参数来确定。如果给的参数类型是 EXACTLY 的话,说明它的父控件给他的大小是确定的,这个时候的大小就填写参数中的数值大小就好(需要 MeasureSpec.getSize(int))。如果参数类型是 AT_MOST 的时候,这个表示父布局给了一个值,当前的 View 的大小不能超过这个值。那么我们就需要自己计算出这个 View 想要的大小,然后和父布局给的最大的值做比较,选择一个值给 setMeasuredDimension()

那么如何获取此 ViewGroup 的正确高度呢?做法就是要获取到每个子View 的高度和一些 padding Margin 加起来就是这个 ViewGroup 应该的高度了。

要想获取子 View 的高度就需要调用 child.measure() 然后 child.getMeasureHeight 就获取 Child 的高度了。也就是说需要我们给子 View 测量一下,测量的时候我们需要传入值。当然这个值也不是随便传入的,如果你随便传入的话,那么 child 的大小就乱了,和你在布局文件中设定的大小就不一样了。

那么如果正确的给 child 传入值呢?LinearLayout 是这样做的,当然我们可以根据我们想要的布局来进行自定义。

// 核心代码

// count 是 child 的个数
for(int i=0;i<count;i++){
// 获取 child 的 LayoutParmas 这个对象有我们在 xml 中给 view 设置的大小信息
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
// 然后根据 LayoutParams 中的参数和 ViewGroup本身的 widthMeasureSpec 来进行对比,选择一个合适的数值给
// child LinearLayout 具体的做法是通过
// ViewGroup 中的 getChildMeasureSpec 方法来获取一个合适值 } // ViewGroup 中的 getChildMeasureSpec(int spec,int padding,int childDimension) 方法的实现代码 // spec 是 onMeasure 中的 spec padding 是子View 的margin + 父控件的 padding childDimension 是子 View 在布局文件中给定的大小
public static int getChildMeasureSpec(int spec,int padding,int childDimension){
int specMode = MeasureSpec.getMode(spec);
int SpecSize = MeasureSpec.getSize(spec);
// 得出 ViewGroup 实际可以使用的大小
int size = Math.max(0,specSize-padding); int resultSize = 0;
int resultMode = 0;
// 然后就是根据 specMode 和 childDimension 来得出合适的大小。
}

布局 onLayout

onLayout 对于子控件来说没有什么意义,对于 ViewGroup 来说,onLayout 方法内部要对子控件进行布局,调用子控件的 layout 函数。

onLayout 重写的时候,只需要获取子 View 的实例,然后调用子 View 的 layout 方法来实现布局就可以了,具体 layout 中传入的参数,是重写 onLayout 的重点。需要通过 getMeasureHeight 等获取子 View 的理想高度,然后再根据具体情况传入数值。

绘制 onDraw

onDraw() 函数就是来绘制了,一般 ViewGroup 不会实现内部的方法,子控件才重写 onDraw() 方法。也是内部一层层分发绘制。呈现树状结构

// 最根部调用下面的方法
// public void draw(Canvas canvas);
// 然后此方法内部调用 onDraw()(针对于 子View的)dispatchDraw(Canvas canvas) (主要是针对于 ViewGroup 的)
// 然后 dispatchDraw() 内部会调用 drawChild(Canvas canvas,View child,long drawingTime) 然后此方法内部会执行 draw 方法,就这样一层一层下去了。如果最终到了子View就会终止,因为子View dispatchDraw 方法体是空的。 //

另外可以认为这三个方法都对应着 measure()layout() draw() 方法。可以认为这三个方法内部调用了上面的方法。

上面 onMeaure onLayout onDraw() 都介绍完了,那么最根处的 View 是怎么调用的呢?

可以看到上面这张图,追溯到根View DecorView ,其实最开始就是 ViewRootImp 来调用 DecorView 的 measure() ,并且传入了具体的值,这个值一般就是页面的大小。然后在 DecorView 的 measure 方法内部会调用 onMeasureonMeasure 的内部又会调用它的子 View 的 measure 然后就这样一层层的下去了,直到所有子 View 执行完毕,DecorView 的 measure 就执行完毕了,到此整个页面的测量工作完成。

onLayout 也是最先 ViewRootImp 来调用 DecorView 的 layout() 开始。onDraw 也是最先 ViewRootImp 来调用 DecorView 的 draw() 开始的。然后 draw() 的内部的执行就和上面介绍 onDraw() 中一样了

到此整个页面的测量、布局、绘制就全部分析完毕了。

可以查看:Activity 从启动到布局绘制的简单分析

View 的绘制过程的更多相关文章

  1. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...

  2. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

  3. android 中view的绘制过程

    view的绘制过程中分别会执行:onMeasure(会多次)计算view的大小,OnLayout(),确定控件的大小和位置 onDraw()绘制view 当Activity获得焦点时,它将被要求绘制自 ...

  4. Android View的绘制过程

    首先是view的绘制过程~最主要的分三部分 measure layout draw 看字面意思,计算,布局,画~ android中控件相当于是画在一个无限大的画布上的,那就产生了几个问题 画布无限大, ...

  5. Android UI 绘制过程浅析(二)onMeasure过程

    前言 View的绘制过程分为 measure.layout.draw三个步骤,接下来对这三个步骤逐一进行研究. measure方法的签名 public final void measure(int w ...

  6. view的绘制原理

    转:http://blog.csdn.net/berber78/article/details/42069301 自定义UI控件,需继承 View类或View的子类,并重载View类中的一些方法,不必 ...

  7. [Android学习笔记]View的draw过程学习

    View从创建到显示到屏幕需要经历几个过程: measure -> layout -> draw measure过程:计算view所占屏幕大小layout过程:设置view在屏幕的位置dr ...

  8. Android探究之View的绘制流程

    Android中Activity是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图 ...

  9. Android - View的绘制流程一(measure)

    该博文所用的demo结构图: 相应的代码: MainActivity.java: [java] view plain copy <span style="font-family:Mic ...

随机推荐

  1. C语言基础 -- 变量

    常用变量类型 ​​ 地址 小端 低地址保存低位,高地址保存高位 常用于 PC(复杂指令集) 大端 低地址保存高位,高地址保存低位 常用于 ARM/手机/网络(精简指令集)

  2. SVN常用命令详解

    命令的使用1.检出 svn co http://路径(目录或文件的全路径) [本地目录全路径] --username 用户名 --password 密码svn co svn://路径(目录或文件的全路 ...

  3. 三分钟带你入门 redis 高可用架构之哨兵

    什么是哨兵? 哨兵(Sentinel)是 redis 的高可用性解决方案,前面我们讲的主从复制它是高可用的基础,需要人工介入才能完成故障转移,哨兵可以解决这个问题,在主从复制情况下,当主节点发生故障时 ...

  4. Prometheus 安装

    目录 简介 安装部署 环境准备 安装 配置环境变量 配置 启动 简介 prometheus存储的是时序数据,即按相同时序(相同名称和标签),以时间维度存储连续的数据的集合. 时序(time serie ...

  5. warning: rpmts_HdrFromFdno: Header V4 DSA/SHA1 Signature, key ID XXXXXX: NOKEY

    我在使用Centos时,会出现这种错误: 本人实践有效的办法是: 加上"--nogpgcheck"参数 就是在你要执行的语句后面加上该参数就行了! 我当时是为了安装jenkins时 ...

  6. Docker部署Mysql集群

    单节点数据库的弊病 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 单节点MySQL的性能瓶领颈 2016年春节微信 ...

  7. 2019-2020-4 20199317《Linux内核原理与分析》第四周作业

    第3章  MenuOS的构造 1  Linux内核源代码简介        计算机的“3大法宝”:存储程序计算机.函数调用堆栈和中断. 操作系统的“两把宝剑”:一把是中断上下文的切换——保存现场和恢复 ...

  8. 大规模机器学习在LinkedIn预测模型中的应用实践

    预测模型在 LinkedIn 的产品中被广泛应用,如 Feed.广告.工作推荐.邮件营销.用户搜索等.这些模型在提升用户体验时起到了重要的作用.为了满足建模需求,LinkedIn 开发并且开源了 Ph ...

  9. 转:解决Eclipse中.properties文件中文乱码问题

    在.properties文件写注释时,发现中文乱码了,由于之前在idea中有见设置.properties文件的编码类型,便找了找乱码原因 在中文操作系统中,Eclipse中的Java类型文件的编码的默 ...

  10. cf round 598div3 D.Binary String Minimizing

    题目:https://codeforces.com/contest/1256/problem/D 题意:给你长度为n的01串,能将任意两相邻字符交换k次,求最小字典序的交换结果. 思路:贪心...甚至 ...