每日一问:简述 View 的绘制流程
Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要。网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追求短平快,所以本文笔者尽量精简。
想必大多数 Android 开发都知道自定义 View 需要关注的几个方法:onMeasure()
、onLayout()
和 onDraw()
,这其实也是每个 View 至关重要的绘制流程。
基本绘制都是会从根视图 ViewRoot
的 performTraversals()
方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。performTraversals()
的核心代码如下:
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制流程
performDraw();
}
measure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
每个 View 都有自己的大小,所以基本自定义 View 的时候都需要重写 onMeasure()
这个方法,以定制化我们的 View 的宽高。如果不重写这个方法,我们通常会出现 wrap_content
和 match_parent
是一样的显示效果。至于原因,其实一探源码便知。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
可以看到,View
默认是会使用 getDefaultSize()
方法进行设置宽高的,在 AT_MOST
和 EXACTLY
两种情况下都会直接使用测量规格里面的尺寸。在 UNSPECIFIED
模式下会直接取getSuggestedMinimumWidth()
的返回值。
getSuggestedMinimumWidth()
会直接根据是否设置backgroud
来进行计算,需要注意的是,直接设置 color 作为backgroud
也会直接采用minXXX
的值。
在 ViewGroup
中,并没有去重写 View
的 onMeasure()
方法,而这都需要它的子类根据自己的逻辑去实现,比如 LinearLayout
和 RelativeLayout
明显测量逻辑是不一样的。不过,ViewGroup
倒是提供了一个 measureChildren()
方法来依次遍历每个子 View 对其进行测量。
在经过 onMeasure()
操作后,getMeasureWidth()
和 getMeasureHeight()
方法就可以拿到正确的返回值了。
由于 View 的 measure 过程和 Activity 的生命周期方法不是同步执行的,如果 View 还没有测量完毕,那么获得的宽/高就是 0。所以在
onCreate()
、onStart()
、onResume()
中均无法正确得到某个 View 的宽高信息。可以通过在onWindowFocusChanged()
判断获取到焦点后进行获取,或者使用view.post()
方式。
layout()
public void layout(int l, int t, int r, int b)
我们可以重写的 onLayout()
方法主要作用是确定子 View 的显示位置,由于 View 已经是最小的层级,所以我们在自定义 View 的时候通常不需要管这个方法,而在自定义 ViewGroup 的时候就不得不注意这个方法了。
经过 onLayout()
流程后,我们的 left
、right
、top
、bottom
得以赋值,所以这时候可以通过 getWidth()
和 getHeight()
方法来获取 View 的实际宽高了。
注意:在 View 的默认实现中,View 的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于 View 的
measure
过程,而最终宽/高形成于 View 的layout
过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。在一些特殊的情况下则两者不相等:
draw()
public void draw(Canvas canvas)
绘制的流程也就是通过调用 View 的 draw()
方法实现的。draw()
方法里的逻辑看起来更清晰,我就不贴源码了。一般是遵循下面几个步骤:
- 绘制背景 –
drawBackground()
- 绘制自己 –
onDraw()
- 绘制孩子 –
dispatchDraw()
- 绘制装饰 –
onDrawScrollbars()
由于不同的控件都有自己不同的绘制实现,所以V iew 的 onDraw()
方法肯定是空方法。而 ViewGroup 由于需要照顾子 View 的绘制,所以肯定在 dispatchDraw()
方法里遍历调用了child的 draw()
方法。
参考:
Android View的绘制流程
https://blog.csdn.net/yisizhu/article/details/51527557
每日一问:简述 View 的绘制流程的更多相关文章
- Android的自定义View及View的绘制流程
目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...
- 深入理解 Android 之 View 的绘制流程
概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...
- 自定义控件(视图)1期笔记02:View的绘制流程
1. 引言: 来自源码的3个方法: (1)public final void measure():测量,用来控制控件的大小,final不建议覆写 (2)public void layout():布局, ...
- Android探究之View的绘制流程
Android中Activity是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图 ...
- 【转】深入理解Android之View的绘制流程
概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...
- 自定义view:view的绘制流程
1.view的绘制流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw. ...
- 深入了解View的绘制流程
1. ViewRoot ViewRoot是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure.layout.draw)都是通过ViewRoot完 ...
- Android之View的绘制流程
本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...
- Android View的绘制流程
写得太好了,本来还想自己写的,奈何肚里墨水有限,直接转吧.正所谓前人种树,后人乘凉.. View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发 ...
随机推荐
- python 做词云图
#导入需要模块 import jieba import numpy as np import matplotlib.pyplot as plt from PIL import Image from w ...
- 创建Maven项目时,GroupId和Artifact Id该怎么填写呢?
1.什么是groupid和artifactId? groupid和artifactId被统称为“坐标”是为了保证项目唯一性而提出的,如果你要把你项目弄到maven本地仓库去,你想要找到你的项目就必须根 ...
- docker安装mysql8
docker run --restart=always -d -v /opt/data/conf.d/:/etc/mysql/conf.d/ -v /opt/data/mysql/:/var/lib/ ...
- java如何消除太多的if else判断?
1.简介 if判断语句是很多编程语言的重要组成部分.但是,若我们最终编写了大量嵌套的if语句,这将使得我们的代码更加复杂和难以维护. 让我们看看能否使用别的方式来做呢. 设计模式是为了更好的代码重用性 ...
- .Net工具类--表达式目录树解析DataReader和DataTable
一.概述 在项目中经常会使用SQL去操作数据库,在读取数据的时候返回结果一般是DataReader和DataSet,其中DataaSet里面可以包含多个DataTable. 读取到数据之后,一般情况下 ...
- 使用Alipay代码源,构建自己的Docker镜像
1. alipay 镜像仓库 地址 (自行换成自己的阿里镜像云DockerHub地址) https://cr.console.aliyun.com/repository/ 2.alipay 代码云 地 ...
- 从零开始一步一步搭建Ubuntu Server服务器、修改数据源、安装Docker、配置镜像加速器、Compose部署Gitlab服务
场景 最终目的是使用Docker Compose部署一个Gitlab服务. 效果 注: 博客: https://blog.csdn.net/badao_liumang_qizhi关注公众号 霸道的程序 ...
- Spring Boot 实战 入门
目前没有系统学习过 Spring 框架,参与工作时,直接参与到了 Spring Boot 项目的开发.目前还比较菜,所以,你要是和我一样,不妨也跳过 Spring 框架的学习,直接学习 Sring B ...
- css3+JS实现幻灯片轮播图
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 易语言网页登录 POST
使用精易模块 打开网页登录窗口 抓取数据 输入用户名密码,抓包 保存抓到的数据 包含post地址和数据信息 易语言代码 解决乱码