源码基于安卓8.0分析结果
ViewStub
是一种不可见的并且大小为0的试图,它可以延迟到运行时才填充inflate
布局资源,当Viewstub
设为可见或者是inflate
的时候,就会填充布局资源,这个布局和普通的试图就基本上没有任何区别,比如说,加载网络失败,或者是一个比较消耗性能的功能,需要用户去点击才可以加载!从而这样更加的节约了性能。对安卓布局很友好!ViewStub
用法1
2
3
4
5
6
7
8
9<ViewStub
android:padding="10dp"
android:background="@color/colorPrimary"
android:layout_gravity="center"
android:inflatedId="@+id/view_stub_inflateid"
android:id="@+id/view_stub"
android:layout="@layout/view_stub_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />这篇文章安卓代码、图片、布局、网络和电量优化说如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 —–2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因
在这里记住一点,如果在
ViewStub
标签中设置了android:inflatedId="@+id/view_stub_inflateid"
,在layout
布局中的根布局在设置android:id="@+id/view_stub_layout"
,这个id
永远找出来都是为null的,原因会在下面说明1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="10dp"
android:id="@+id/view_stub_layout"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_marginTop="20dp"
android:id="@+id/imageview"
android:padding="10dp"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>在
activity
或者是fragment
中的使用,mViewStub.getParent()==null
就是说明没有被填充,需要填充,如果填充了,那么它的parent
不会为null,具体的骚操作,后续我介绍View
的绘制流程的时候在详细说明。第一种使用的方法
1
2
3
4
5mViewStub = findViewById(R.id.view_stub);
if (null!=mViewStub.getParent()){
View inflate = mViewStub.inflate();
....
}第二种方式:
mViewStub.setVisibility(View.VISIBLE);
和inflate()
方法一样。1
2
3
4
5mViewStub = findViewById(R.id.view_stub);
if (null!=mViewStub.getParent()){
mViewStub.setVisibility(View.VISIBLE);
....
}第三种方式,
my_title_parent_id
是layout
的根布局的id
1
2
3
4
5
6
7
8
9mViewStub = findViewById(R.id.view_stub);
// 成员变量commLv2为空则代表未加载 commLv2 的id为ViewStub中的根布局的id
View commLv2=findViewById(R.id.my_title_parent_id);
if ( commLv2 == null ) {
// 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象
commLv2 = (View)mViewStub.inflate();
} else {
// ViewStub已经加载
}ViewStub
构造方法,注意获取了几个值mInflatedId
就是android:inflatedId="@+id/find_view_stub"
这个值,mLayoutResource
就是layout
的resId,ViewStub的mID
id。可以看出ViewStub
是View
的子类.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public final class ViewStub extends View {
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
// TODO: 2018/5/23 ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
//不可见
setVisibility(GONE);
// 设置不绘制
setWillNotDraw(true);
}
}在构造方法中:同时注意不可见
setVisibility(GONE);
,设置不绘制setWillNotDraw(true);
,同时通过下面的方法看出,ViewStub 是一个大小为0的视图。1
2
3
4
5
6
7
8
9
10
11
12
13@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 宽高都为0 onMeasure的时候 宽高都为0
setMeasuredDimension(0, 0);
}
//todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}关于
inflate()
方法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
27public View inflate() {
// 1、获取ViewStub的parent view,也是目标布局根元素的parent view
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
/// 2、加载目标布局 牛逼的方法
final View view = inflateViewNoAdd(parent);
// 3、将ViewStub自身从parent中移除
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
// TODO: 2018/5/23 必须设置布局的文件
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
// TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}第一点,ViewStup也只能在ViewGroup中使用,不能在View中去使用,要不然会抛出异常
IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
- 第二点,也必须设置
layout
属性,要不然也会抛出异常throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
;
关于方法
inflateViewNoAdd(parent);
1
2
3
4
5
6
7
8
9
10
11
12
13
14private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent, false);
//和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null 非常有些意思
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}第一点,底层调用的还是
LayoutInflater.from(mContext).inflate(mLayoutResource, parent, false);
,Android源码分析(LayoutInflater.from(this).inflate(resId,null);源码解析),这里不做过多的说明第二点,又看到这个方法,似曾相识,对,这也是为什么
ViewStub
找不到根布局id的原因,因为mInflatedId != NO_ID
,就会view.setId(mInflatedId);
1
2
3if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}将ViewStub自身从parent中移除
replaceSelfWithView(view, parent);
,具体的原因,这里不做分析,因为有点小复杂,这里就大概明白就行,对于理解这个ViewStub不困难,哈哈1
2
3
4
5
6
7
8
9
10
11
12
13
14private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 3、将ViewStub自身从parent中移除
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
// 4、将目标布局的根元素添加到parent中,有参数
parent.addView(view, index, layoutParams);
} else {
// 5、将目标布局的根元素添加到parent中
parent.addView(view, index);
}
}这里使用到了弱引用,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,不论当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用,为了更好地释放内存,安卓代码、图片、布局、网络和电量优化这篇文章有很好的解释,而且这个
mInflatedViewRef
只在这里初始化,如果说没有调用inflate
的方法的话,这个对象一定为null
;1
2
3
4
5
6//更好的释放内存
private WeakReference<View> mInflatedViewRef;
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view)
}为啥
setVisibility(View.VISIBLE)
等同于inflate
,原因是ViewStub
进行了重写。可以看出代码的逻辑,只要没有调用过,inflate()
方法,setVisibility(VISIBLE )
和setVisibility(INVISIBLE)
这个两个参数走的方法一样,只不过,一个看不到,实际上的位置已经确定了(INVISIBLE)。但是如果调用多次的话setVisibility()
记得也得判断下null!=mViewStub.getParent()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
// TODO: 2018/5/23 弱引用的使用
//如果已经加载过则只设置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();// 调用inflate来加载目标布局
}
}
}贴出全部的代码,有空的话,可以研究下。
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310@RemoteView
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
// TODO: 2018/5/23 弱引用:弱引用是比软引用更弱的一种的引用的类型,
// 只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,
// 不敢当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用
//更好的释放内存
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
this(context, 0);
}
/**
* Creates a new ViewStub with the specified layout resource.
*
* @param context The application's environment.
* @param layoutResource The reference to a layout resource that will be inflated.
*/
public ViewStub(Context context, @LayoutRes int layoutResource) {
this(context, null);
mLayoutResource = layoutResource;
}
public ViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
// TODO: 2018/5/23 ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
//不可见
setVisibility(GONE);
// 设置不绘制
setWillNotDraw(true);
}
/**
* Returns the id taken by the inflated view. If the inflated id is
* {@link View#NO_ID}, the inflated view keeps its original id.
*
* @return A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@IdRes
public int getInflatedId() {
return mInflatedId;
}
/**
* Defines the id taken by the inflated view. If the inflated id is
* {@link View#NO_ID}, the inflated view keeps its original id.
*
* @param inflatedId A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #getInflatedId()
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
public void setInflatedId(@IdRes int inflatedId) {
mInflatedId = inflatedId;
}
/** @hide **/
public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
mInflatedId = inflatedId;
return null;
}
/**
* Returns the layout resource that will be used by {@link #setVisibility(int)} or
* {@link #inflate()} to replace this StubbedView
* in its parent by another view.
*
* @return The layout resource identifier used to inflate the new View.
*
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@LayoutRes
public int getLayoutResource() {
return mLayoutResource;
}
/**
* Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
* or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
* used to replace this StubbedView in its parent.
*
* @param layoutResource A valid layout resource identifier (different from 0.)
*
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
public void setLayoutResource(@LayoutRes int layoutResource) {
mLayoutResource = layoutResource;
}
/** @hide **/
public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
mLayoutResource = layoutResource;
return null;
}
/**
* Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
* to use the default.
*/
public void setLayoutInflater(LayoutInflater inflater) {
mInflater = inflater;
}
/**
* Get current {@link LayoutInflater} used in {@link #inflate()}.
*/
public LayoutInflater getLayoutInflater() {
return mInflater;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 宽高都为0 onMeasure的时候 宽高都为0
setMeasuredDimension(0, 0);
}
//todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
/**
* When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
* {@link #inflate()} is invoked and this StubbedView is replaced in its parent
* by the inflated layout resource. After that calls to this function are passed
* through to the inflated view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
*
* @see #inflate()
*/
@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
// TODO: 2018/5/23 弱引用的使用
//如果已经加载过则只设置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();// 调用inflate来加载目标布局
}
}
}
/** @hide **/
public Runnable setVisibilityAsync(int visibility) {
if (visibility == VISIBLE || visibility == INVISIBLE) {
ViewGroup parent = (ViewGroup) getParent();
return new ViewReplaceRunnable(inflateViewNoAdd(parent));
} else {
return null;
}
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent, false);
//和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null 非常有些意思
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
// TODO: 2018/5/23 关注他
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 3、将ViewStub自身从parent中移除
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
// 4、将目标布局的根元素添加到parent中,有参数
parent.addView(view, index, layoutParams);
} else {
// 5、将目标布局的根元素添加到parent中
parent.addView(view, index);
}
}
/**
* Inflates the layout resource identified by {@link #getLayoutResource()}
* and replaces this StubbedView in its parent by the inflated layout resource.
*
* @return The inflated layout resource.
*
*/
public View inflate() {
// 1、获取ViewStub的parent view,也是目标布局根元素的parent view
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
/// 2、加载目标布局 牛逼的方法
final View view = inflateViewNoAdd(parent);
// 3、将ViewStub自身从parent中移除
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
// TODO: 2018/5/23 必须设置布局的文件
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
// TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
/**
* Specifies the inflate listener to be notified after this ViewStub successfully
* inflated its layout resource.
*
* @param inflateListener The OnInflateListener to notify of successful inflation.
*
* @see ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
/**
* Listener used to receive a notification after a ViewStub has successfully
* inflated its layout resource.
*
* @see ViewStub#setOnInflateListener(ViewStub.OnInflateListener)
*/
public static interface OnInflateListener {
/**
* Invoked after a ViewStub successfully inflated its layout resource.
* This method is invoked after the inflated view was added to the
* hierarchy but before the layout pass.
*
* @param stub The ViewStub that initiated the inflation.
* @param inflated The inflated View.
*/
void onInflate(ViewStub stub, View inflated);
}
/** @hide **/
public class ViewReplaceRunnable implements Runnable {
public final View view;
ViewReplaceRunnable(View view) {
this.view = view;
}
@Override
public void run() {
replaceSelfWithView(view, (ViewGroup) getParent());
}
}
}最后做了一张图
- 说明一下
ViewStub
的原理很简单!好吧,这个有点皮