Android UI制作流程
环境
- Java 11
- android 11
因为学习的课程api 不一致 导致源码有些关键办法无法进入仔细阅读 采用截图的办法理解思路
view添加到窗口
进入到源码中能够发现 ,每个activity 默许生成的代码中都会有一个setContentView办法,这个办法用于加载输入路径当前的布局以及后续的操作
底层本质仍是调用一个托付去设置资源
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
在持续走进这个托付类的接口完结办法
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
ensureSubDecor()
进入这个decor 办法 下图为部分代码
if (!mSubDecorInstalled)
mSubDecor = createSubDecor();
createSubDecor()
依据实装值 来判别是否创立对应的decor
这个办法中有大量的体系资源整合 ,都是一些体系主题的装修器放入到对应需求创立的window中,比方
- content
- themedContext
- subDecor 等等体系资源
再持续往下看 会看到一个赋值操作 将 mWindow.setContentView(subDecor);
赋值,
PS: WINDOW 目标的主要完结都在phone window 中 所以咱们去phone window 寻觅这个主要的办法
这个办法有两个主要完结 这儿咱们主要看 layouResId为参数的完结内容
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
// 先判别上下文父目标是否存在 是否创立内容装修器(用于拿到需求的根底主题资源)
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这儿咱们点进 generateLayout
办法,见名知意 这个办法主要是组装布局需求的主题属性和样式属性 这儿看到
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
加载资源的办法 依据不同的 feature 给layout赋不同的值,之后将布局信息添加到 mdecor
之后再 generateLayout() 回来一个 contentParent 也便是一个viewGroup
流程图
总结
- 在进入到activity 之后 会优先创立顶层布局 decorView 来设置体系需求的主题参数
- 将decorView 添加到根底布局中 回来 viewGroup 不同的主题加载不同的viewGroup 但是都会有一个容器OnDraw的id
- 最后被setContentView添加到布局中
View的制作流程
依据上图 咱们能够先去检查ActivityThread
找到 H这个类 他承继自 handle 内部完结了一个handleMessage
办法 内部会有一个调用链路
- handleMessage 调用 handleLaunchActivity 标识 启动一个挑选的activity
- handleLaunchActivity 调用 handleResumeActivity
- handleResumeActivity 调用performResumeActivity终究在这个办法回调
需求留意的是在这个调用链路中 viewRootImpl performTraversal()
这个办法 ,制作view的三个步骤便是在这个线程中完结的
丈量
从最底层源码的丈量能够看出 履行丈量其实便是不断调用到最低层的 setMeasureDimensionRow 中对两个成员变量标记赋值,保存丈量控件的宽高。
在view 制作之前 安卓底层会先去使用 measure
丈量对应view 的形式和尺度,安卓给咱们供给了一个叫做 measurespec类 是一个三十二位int的存储值,前两位代表形式,后三十位代表尺度
- SpecMode(前两位) + SpecSize(后三十位)
核心办法
makeMeasureSpec
用于取值 mode 只需求取前两位 size 只需求取后三十位 组合成一个 MeasureSpec
相同 MeasureSpec 也有对应的getsize
和getmode
获取对应的值
getRootMeasureSpec
参数
- 子容器高度/宽度
- 尖端容器 高度/宽度
依据设置的布局属性 来设置对应的mode
协助 performMeasure 是拿到需求的 measurespec
decor view 承继自 framelayout 检查他的 onMeasure 办法
通过递归的办法 判别并且设置不同规则父容器和子控件的丈量办法
View 丈量的对比图
完整丈量自身流程
ps: 在自定义view 的时候 一定要重写 onmeasureDiemsion办法
在 view 设置丈量自己之前会调用getdefaultsize 依据不同的形式获取到size 有趣的是 at 和 exactly 赋值都是父容器剩余的大小,所以在自定义view 需求重写一系列办法否则就可能出现问题
自定义容器和viwe 区别 :
- 自定义容器需求先丈量子控件再量自己
- view 直接丈量自己即可
布局
核心办法
view.setFrame()
对控件的上下左右赋值 设置完结 控件需求摆放的方位就确定了
view.onlayout()
如果此刻是容器 则需求调用此办法 确定子控件的方位
制作
核心办法
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)
* 7. If necessary, draw the default focus highlight
*/
viewGroup.dispatchDraw()
总结
制作过程
ViewGroup
- 制作布景
- 制作自己
- 制作 子控件
- 制作远景 滚动条等
View
- 制作布景
- 调用自己的 ondraw
- 制作远景 滚动条等