Android标签

ViewStub 懒加载

最开始接触ViewStub是从DecorView中认识的,都知道DecorView是顶级的View,它是一个线性布局,它有两个子View:ViewStubFrameLayout,帧布局中包括一个FitWindowsLinearLayout布局,FitWindowsLinearLayout包含ViewStubCompatContentFrameLayout,而我们使用setContentView放进去的布局文件就是放在ContentFrameLayout这个布局之中。而我们最开始就接受到的知识是DecorView是两个部分组成的,一个是titleView,另一个就是ContentView(可以用findViewById(android.R.id.content)获取)。

你想输入的替代文字
ViewStub也是继承自View,它使用的是惰性加载的方式,也就是即使它已经包含于布局文件中,但是不主动加载,它就是为空的。ViewStub一般用于需要展示另外的一个效果,如网络加载失败的页面显示。

ViewStub的加载原理

ViewStub只能加载一次,重复加载会导致异常,这是因为ViewStub只要加载一次自身就会被移除,并把自身所包含的内容传递给父布局。

1
2
3
4
5
<ViewStub
android:id="@+id/vs"
android:layout="@layout/activity_detail"
android:layout_width="match_parent"
android:layout_height="match_parent" />

调用的ViewStub的inflate()方法就会加载布局,还有一种加载它的方法,在它的setVisibility方法的源码中可以看到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}

当你设置visibility为VISIBLEINVISIBLE就会调用inflate()去加载

include

include的用法也很简单,它的出现提高了布局的可重用性,使得更方便控制管理。

1
2
3
4
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />

使用include可能遇到的坑:
1、include标签当中可以重写layout的所有属性,在里面覆写的layout属性会覆盖掉实际layout(如上就是app_bar_main)布局的layout属性),另外需要注意的是如果要覆写layout属性的话,必须要覆写layout_widthlayout_height,Android Studio一般也会提示。
2、一个布局文件中如果有多个include标签需要设置id才能找到对应的子控件,否则只能找到第一个include的布局文件及控件
3、如果你在include标签的地方和加载的根布局文件都设置了id,需要保持一致,否则可能获取不到根布局对象。

merge

merge标签是配和include标签使用的,作为include的根布局标签,意义是帮助include标签去除一层多余的ViewGroup,具体很简单就不用多说,想想就明白了。
使用merge的注意事项:
1、根布局是FrameLayout且不需要设置backgroundpadding等属性,可以用merge标签代替,因为setContentView就是加载到帧布局之中的。
2、因为merge不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,第三个参数必须为true,由于merge不是View,所以对merge标签的所有设置都是无效的。

附录:一个求Activity最大层级的算法

在网上看到过一个递归版本的这种算法,那既然递归版本的有了,就来一个非递归版本的。其实这个算法可以看做是一颗树(因为View的层级就是一棵View树),那其实就是利用树的层次遍历,算出最深的节点,树的层次遍历怎么实现也就很简单拉,就是把每一层从左到右依次放入一个队列中,从队列头部取出,然后将它的子节点放到队列末尾,一直循环执行下去知道队列为空。那我们并不要这个层次遍历输出的结果,我们只需要最大深度该怎么实现,难搞啊。
不慌,问题不大,我们可以在一层子节点的队列尾部加上一个标志,等到队列的同级的节点都出了队列的时候,就到了这个标志,就意味着一层已经遍历完了,然后再将这个标志移到队列的尾部,继续执行,那不就行了,但是要注意,如果没有下一层,那么一直移动就会造成死循环,所以排除掉这种情况。好,下面上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void findDeep() {
ViewGroup viewGroup = (ViewGroup) getWindow().getDecorView();
//TickView一个自定义View,用来当做标志位
TickView tickView = new TickView(getApplicationContext());
Queue<View> queue = new LinkedList<>();
queue.add(viewGroup);
queue.add(tickView);
int deep = 0;
while (!queue.isEmpty()) {
if (queue.peek() instanceof ViewGroup) {
viewGroup = (ViewGroup) queue.poll();
int count = viewGroup.getChildCount();
if (count > 0) {
for (int i=0;i<count;i++){
if (viewGroup.getChildAt(i) instanceof ViewGroup){
queue.offer(viewGroup.getChildAt(i));
}
}
}
} else if (queue.peek() instanceof TickView) {
queue.poll();
deep++;
if (!queue.isEmpty()){
queue.offer(tickView);
}
}
}
Log.d(TAG, "findDeep: "+deep);
}