网站首页 > 博客文章 正文
Android UI界面架构
每个Activity包含一个PhoneWindow对象,PhoneWindow设置DecorView为应用窗口的根视图,在里面就是TitleView和ContentView, 平时使用的setContentView()就是设置的ContentView。
View控件
Android中控件基本分为两类View和ViewGroup,ViewGroup作为容器管理View。Android视图,是类似于Dom树的架构,如下图:
如何绘制View
当startActivity启动一个Activity时,会绘制当前Activity的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个ViewGroup负责让自己的子View被绘制,每一个View负责绘制自己,通过draw()方法,绘制过程分三步走:
- Measure: 测量
- Layout:定位
- Draw:绘制及显示
整个绘制流程是在ViewRoot中的performTraversals()方法展开的。部分源代码如下:
private void performTraversals() {
......
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
View绘制的流程图如下:
Measure
Measure过程会为一个 View 及所有子节点的 mMeasuredWidth 和 mMeasuredHeight 变量赋值,该值可以通过 getMeasuredWidth()和getMeasuredHeight()方法获得。
onMeasure()方法核心源码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//设置View宽高的测量值
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
//measureSpec指的是View测量后的大小
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//MeasureSpec.UNSPECIFIED一般用来系统的内部测量流程
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//我们主要关注着两种情况,它们返回的是View测量后的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//如果View没有设置背景,那么返回android:minWidth这个属性的值,这个值可以为0
//如果View设置了背景,那么返回android:minWidth和背景最小宽度两者中的最大值。
protected int getSuggestedMinimumHeight() {
int suggestedMinHeight = mMinHeight;
if (mBGDrawable != null) {
final int bgMinHeight = mBGDrawable.getMinimumHeight();
if (suggestedMinHeight < bgMinHeight) {
suggestedMinHeight = bgMinHeight;
}
}
return suggestedMinHeight;
}
通过源码可以看出,最终的高宽是调用setMeasuredDimension()设定的,如果不重写,默认是直接调用getDefaultSize获取尺寸的。
MeasureSpec是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。
- EXACTLY
精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。 - AT_MOST
最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸。 - UNSPECIFIED
不指定测量模式,View想多大就多大。
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
Layout
通过onMeasure()方法我们已经知道了View的大小,那么接下来就是确定该View到底在哪个位置显示,这个时候就要调用layout()方法。
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
.....
onLayout(changed, l, t, r, b);
.....
}
layout获取四个参数,左,上,右,下坐标,相对于父视图而言。
通过上面performTraversals()方法中的源码:
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
这里可以看到,就是使用了onMeasure()测量的宽和高,作为后面两个参数。
当layout结束以后getWidth()与getHeight()才会返回正确的值。
getWidth/Height() 和 getMeasuredWidth/Height()有什么区别?
下图用于比较形象的展示getHeight()和getMeasuredHeight():
- getHeight(): View在设定好布局后View的高度。
- getMeasuredHeight(): 对View上的內容进行测量后得到的View內容占据的高度。
getWidth()和getMeasuredWidth()与高度一致。
Draw
绘制流程如下:
总结一下步骤其实就是:
- view绘制自身(包含背景,自身内容)
- 绘制装饰(滚动指示器,滚动条等)
如下是draw()方法的核心代码
public void draw(Canvas canvas) {
......
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
Android View常见面试题
- 首次 View 的绘制流程是在什么时候触发的?
- ViewRootImpl 创建的时机?
- ViewRootImpl 和 DecorView 的关系是什么?
- DecorView 的布局是什么样的?
- DecorView 的创建时机?
- setContentView 的流程
- LayoutInflate 的流程
- Activity、PhoneWindow、DecorView、ViewRootImpl 的关系?
- PhoneWindow 的创建时机?
- 如何触发重新绘制?
- requestLayout 和 invalidate 的流程?
- requestLayout 和 invalidate 的区别?
猜你喜欢
- 2025-01-31 福建富昌维控申请人机界面设备识别方法及终端专利,降低识别时间
- 2025-01-31 上海三思取得拉索定位的吊挂式显示屏专利,使显示屏结构稳定
- 2025-01-31 凯晖电子取得分段触控显示屏及具有它的输入设备专利
- 2025-01-31 福建福昕申请通过自定义格式语言完成 3D 模型轻量化编辑与协作专利,实现室内设计跨平台 3D 模型轻量化编辑与协作
- 2025-01-31 FastAI + timm: 快速构建高性能计算机视觉模型
- 2025-01-31 翻译中的创意转化:如何调整视角与结构实现宣传效果
- 2025-01-31 (工具分享)计算机小白专业视频神器-通义万象
- 2025-01-31 最新版camera assistant新增两个功能…
- 2025-01-31 花旗:DeepSeek 潜在效率突破对软件行业的影响
- 2025-01-31 fzf - 强大的模糊搜索工具,一条命令颠覆你的命令行交互体验
你 发表评论:
欢迎- 368℃用AI Agent治理微服务的复杂性问题|QCon
- 365℃手把手教程「JavaWeb」优雅的SpringMvc+Mybatis整合之路
- 358℃初次使用IntelliJ IDEA新建Maven项目
- 351℃Maven技术方案最全手册(mavena)
- 348℃安利Touch Bar 专属应用,让闲置的Touch Bar活跃起来!
- 347℃InfoQ 2024 年趋势报告:架构篇(infoq+2024+年趋势报告:架构篇分析)
- 345℃IntelliJ IDEA 2018版本和2022版本创建 Maven 项目对比
- 343℃从头搭建 IntelliJ IDEA 环境(intellij idea建包)
- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)