0%

高级UI-:(01)2021.9.12-setContentView流程分析---leo老师

首先,setContentView 的主要目的有 2 个:

  • 创建 DecorView: installDecor()

  • 获取到 Content : mLayoutInflater.inflate(xx, xx)

继承 Activity 的情况

如果继承 Activity ,setContentView 的时候源码点进去发现是 getWindow.setContentView() ,我们知道这个getWindow 就是 PhoneWindow 。在 ActivityThread.performActivity 启动 Activity 过程中,会调用 activity.attach ,在 attach 中 会创建 PhoneWindow 对象。之后才会有Activity 的 onCreate 回调。

在 PhoneWindow 的 installDecor 中会调用 generateDecor,其实最终就是直接 new 一个 DecorView:

1
2
3
4
protected DecorView generateDecor(int featureId) {
//省略无关代码
return new DecorView(context, featureId, this, getAttributes());
}

DecorView 实际上就是一个 FrameLayout 。

在 PhoneWindow 的 installDecor 还会生成 contentParent:

1
mContentParent = generateLayout(mDecor);

在里面会根据各种标记位 Flag 来选择不同的 layout xml布局文件作为 contentParent 的布局文件

面试题: 我在 setContentView 之后,再去设置 requestWindowFeature () 方法,为什么会报错?

直接原因:在 setContentView 方法执行完成之后,会将一个标志位置为 true, 等 requestWindowFeature方法调用的时候会判断这个标志位为true则报错。间接原因:为什么这么设计呢?这是因为在 generateLayout 的代码中, requestWindowFeature 设置的值也会作为 Flag 来选择 不同的layoutResource ,比如是否有 title (Window.FEATURE_NO_TITLE)等,所以需要这样 。

状态栏不在 setContentView 中,它是属于系统的。

继承 AppcompatActivity 情况

继承 Activity 的时候,可以使用 requestWindowFeature 来更改一些 Window 属性;但是如果继承 AppcompatActivity ,直接使用 requestWindowFeature 是没作用的,需要使用 supportRequestWindowFeature 方法。

将原始的 windowContentView 的 id 置为 NO_ID ,将自己的 subDecorView 的id 设置成原来的 windowContentView 的 id ,实现了个替换。后来就通过 PhoneWindow 的 setContentView 将 subDecorView 设置进去了。

所以,这个步骤相当于将 DecorView 中的内容完全替换了。

View 是如何根据xml创建的

上述会执行到:

1
LayoutInflater.from(mContext).inflate(resId, contentParent)

那么如何根据 resId 布局文件创建出布局中的哪些 View 呢?

再 createView 中,通过 clazz = Class.forName() 获得这些 View 的 class 对象,之后,通过 newInstance 的形式将 View 创建出来。

需要注意一点,如果 LayoutInflater.from(mContext).inflate 的时候,layout 是一个 merge 的,一定要 有parent 并且 attach 为 true:

1

ViewStub 继承的是 View ,首先它是 Gone 的,在显示的时候将自己的 id 设置为 NO_ID

## 面试题

  1. 为什么requestWindowFeature()要在setContentView()之前调用
    requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,
    在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,
    而这个变量会在 PhoneWindow.setContentView 调用的时候设置为true。
    1. 为什么这么设计呢?
      DecorView的xml布局是通过设置的窗口特征进行选择的。
    2. 为什么 requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
      需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。

2.LayoutInflate几个参数的作用?

LayoutInflater inflater = LayoutInflater.from(this);
// 方式一:布局添加成功,里面执行了 ll.addView(view)View view = inflater.inflate(R.layout.inflate_layout, ll, true);

// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
ll.addView(view);

// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是处于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);

3.描述下merge、include、ViewStub标签的特点
include:

  1. 不能作为根元素,需要放在 ViewGroup中
  2. findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
  3. 为什么会报空指针呢?
    如果使用include标签时设置了id,这个id就会覆盖 layout根view中设置的id,从而找不到这个id
    代码:LayoutInflate.parseInclude
    –》final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
    –》if (id != View.NO_ID) {
    view.setId(id);
    }

merge:

  1. merge标签必须使用在根布局
  2. 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,
    且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
  3. 由于merge不是View所以对merge标签设置的所有属性都是无效的.

ViewStub:就是一个宽高都为0的一个View,它默认是不可见的

  1. 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate()
    或者viewStub.setVisible(View.visible)方法时才内容才变得可见。
  2. 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,
    而是使用对应的layout视图代替。
谢谢你的鼓励