当前位置: 首页 > news >正文

Android核心开发【UI绘制流程解析+原理】

一、UI如何进行具体绘制

UI从数据加载到具体展现的过程:

进程间的启动协作:

二、如何加载到数据

应用从启动到onCreate的过程:

Activity生产过程详解:

核心对象

绘制流程源码路径

1、Activity加载ViewRootImpl

ActivityThread.handleResumeActivity()
--> WindowManagerImpl.addView(decorView, layoutParams)
--> WindowManagerGlobal.addView()

2、ViewRootImpl启动View树的遍历

ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals()(performMeasure、performLayout、performDraw)

二、View绘制流程

1、measure

(1)MeasureSpec是什么?

重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?

留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:

00 000000 00000000 00000000 00000000

而其结构为 mode + size ,前2位为mode,而后30位为size。

==> getMode()方法(measureSpec --> mode):

private static final int MODE_SHIFT = 30;
// 0x3转换为二进制即为:11
// 左移30位后:11000000 00000000 00000000 00000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;public static int getMode(int measureSpec) {// 与MODE_MASK按位与运算后,即将低30位清零,结果为mode左移30位后的值return (measureSpec & MODE_MASK);
}

getSize()方法同理。

==> makeMeasureSpec()方法(mode + size --> measureSpec):

public static int makeMeasureSpec(@IntRange(from = 0,to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}
}

这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:

01000000 00000000 00000000 00000000 |
00001000 00001011 11110101 10101101 =
01001000 00001011 11110101 10101101

==> 测量模式:

public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY  = 1 << MODE_SHIFT;
public static final int AT_MOST  = 2 << MODE_SHIFT;

UNSPECIFIED:父容器不对View作任何限制,系统内部使用。

EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。

AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。

(2)ViewGroup的测量流程

回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;
}

match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。

往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:

protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:

childLayoutParams\parentSpecModeEXACTLYAT_MOSTUNSPECIFIED
dpEXACTLY/childSizeEXACTLY/childSizeEXCATLY/childSize
match_parentEXACTLY/parentSizeAT_MOST/parentSizeUNSPECIFIED/0
wrap_contentAT_MOST/parentSizeAT_MOST/parentSizeUNSPECIFIED/0

回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:

// 加上padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// 检查是否小于最小宽度、最小高度
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 检查Drawable的最小高度和宽度
final Drawable drawable = getForeground();
if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));

综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。

(3)View的测量流程

View.performMeasure()
-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
-->setMeasuredDimension(int measuredWidth, int measuredHeight)
-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)

可以看到setMeasuredDimensionRaw()方法:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {// 存储测量结果mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;// 设置测量完成的标志位mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

View不需要考虑子View的大小,根据内容测量得出自身大小即可。

另外,View中的onMeasure方法中调用到getDefaultSize方法:

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;
}

这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。

2、layout

布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:

// 实则为DecorView的left, top, right, bottom四个信息
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {final int count = getChildCount();// 省略for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();// 省略child.layout(childLeft, childTop, childLeft + width, childTop + height);}}
}

可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。

综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。

3、draw

(1)绘制过程入口

ViewRootImpl.performDraw()
-->ViewRootImpl.draw()
-->ViewRootImpl.drawSoftware()
-->View.draw()

(2)绘制步骤

进入到View的draw方法中,可以看到以下一段注释:

/** 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)*/

image.png

以上就是Android开发中的UI绘制原理及过程实现;更多技术探讨可进入查看《Android核心技术手册》进行学习。

最后

结合draw方法的源码,绘制过程的关键步骤如下:

==> 绘制背景:drawBackground(canvas)

==> 绘制自己:onDraw(canvas)

==> 绘制子view:dispatchDraw(canvas)

==> 绘制滚动条、前景等装饰:onDrawForeground(canvas)

http://www.lryc.cn/news/420.html

相关文章:

  • 计算机组成原理第七章笔记记录
  • ORB-SLAM2编译、安装等问题汇总大全(Ubuntu20.04、eigen3、pangolin0.5、opencv3.4.10)
  • QuickBuck:一款专为安全研究人员设计的勒索软件模拟器
  • 【八大数据排序法】堆积树排序法的图形理解和案例实现 | C++
  • 低代码开发平台|生产管理-生产加工搭建指南
  • Python类型-语句-函数
  • 真兰仪表在创业板开启申购:募资约20亿元,IPO市值约为78亿元
  • 【2023】Prometheus-Prometheus与Alertmanager配置详解
  • 华为HCIE学习之openstack基础
  • Python实现贝叶斯优化器(Bayes_opt)优化BP神经网络分类模型(BP神经网络分类算法)项目实战
  • Elasticsearch(九)搜索---搜索辅助功能(下)--搜索性能分析
  • 化繁为简|中信建投基于StarRocks构建统一查询服务平台
  • 2023数字中国创新大赛·数据开发赛道首批赛题启动报名
  • MySQL数据库
  • 鸿蒙设备学习|快速上手BearPi-HM Micro开发板
  • 软件测试标准流程
  • Python身份运算符
  • linux 安装,卸载jdk8
  • 标准舆情监测平台解决方案及流程,TOOM舆情监测工作计划有哪些?
  • Lombok使用总结
  • Qt 如何处理耗时的线程,不影响主线程响应 QApplication::processEvents)
  • Antd-table全选踩坑记录
  • 防灾必看,边滑坡安全预警解决方案
  • 你每天所做的工作,让你产生了成就感吗?
  • MySQL中的锁
  • WebView自定义进度条、加载动画,拿走直接用~
  • 内存数据库Apache Derby、H2
  • 麻省理工出版 | 2023年最新深度学习综述手册
  • vi命令详解
  • 抖音的外卖行业入局,为中小外卖企业创业者的机会给了哪些机会?