布局優(yōu)化是性能優(yōu)化中一項(xiàng)不可缺失的工作,而ViewStub是性能布局優(yōu)化中很有必要的一項(xiàng),使用ViewStub可以把類似空白頁(yè)、錯(cuò)誤頁(yè)等不需要馬上顯示的View實(shí)現(xiàn)懶加載的效果,而且內(nèi)存占有量非常的少,它是一個(gè)寬高為0、不執(zhí)行draw方法且本身設(shè)置了View.GONE所以基本上不參與layout,非常適合用于做懶加載的布局優(yōu)化。
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// 在構(gòu)造函數(shù)中就設(shè)置成了GONE
setVisibility(GONE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
是吧,這些常見的方法都是空實(shí)現(xiàn)的且在初始化中就設(shè)置成GONE了、所以這就很好奇是怎么實(shí)現(xiàn)懶加載的。
ViewStub簡(jiǎn)單用法
先看下如何使用ViewStub:
<ViewStub
android:id="@+id/id_vs"
android:layout="@layout/layout_test"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
ViewStub mViewStub = (ViewStub) findViewById(R.id.id_vs);
View mRootView = mViewStub.inflate();
// 接下來(lái)就可以各種的findViewById了...
mRootView.findViewById(R.id.xxx);
在布局的時(shí)候我們需要給android:layout傳入一個(gè)我們定義好的布局文件,這個(gè)文件只有在調(diào)用了mViewStub.inflate()才會(huì)被顯示出來(lái)。對(duì)于傳入的的布局文件會(huì)賦值給ViewStub的成員變量mLayoutResource,Ok,一切的準(zhǔn)備就緒了。
ViewStub實(shí)現(xiàn)的原理
原理其實(shí)不難,只要看下下面這個(gè)方法就一目了然了。
public View inflate() {
final ViewParent viewParent = getParent();
// 首先要求父控件是ViewGroup才可以
if (viewParent != null && viewParent instanceof ViewGroup) {
// 其次要給mLayoutResource賦值,因?yàn)閙LayoutResource就是要懶加載顯示的界面對(duì)應(yīng)的布局
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
// 這就是重點(diǎn)了,直接調(diào)用常見的LayoutInflater.from().inflate系列方法來(lái)初始化需要懶加載的View
final View view = factory.inflate(mLayoutResource, parent,
false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
// 通過(guò)返回的這個(gè)View 我們就可以拿來(lái)各種findViewById 就能顯示需要顯示的View了
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
ViewStub這個(gè)類很短去掉注釋估計(jì)也就一百行的代碼,原理其實(shí)就是把對(duì)應(yīng)的布局文件當(dāng)做值傳入給mLayoutResource變量,當(dāng)調(diào)用inflate()的時(shí)候調(diào)用LayoutInflater.from(xx).inflate(xx)方法把對(duì)應(yīng)懶加載的View初始化出來(lái),沒錯(cuò)、這樣子之后就能顯示了。
最后有一個(gè)疑問(wèn)
android:visibility="gone" 和 ViewStub 之間的區(qū)別,為什么GONE就沒有ViewStub的功效呢,因?yàn)橥瑯釉诮缑嫔鲜遣伙@示的。
setContentView(R.layout.activity_main);
這一切還得從這個(gè)方法說(shuō)起,都知道這個(gè)方法最后是會(huì)跳轉(zhuǎn)到LayoutInflater 這個(gè)類中去然后同樣執(zhí)行inflate方法。
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
在inflate中有這么一段方法,會(huì)根據(jù)傳入的布局資源然后調(diào)用XmlResourceParser實(shí)現(xiàn)xml文件解析從而得到一個(gè)個(gè)的View,而這時(shí)候如果你把不需要馬上顯示的View設(shè)置成GONE一樣會(huì)被解析一樣會(huì)被加載到內(nèi)存中去,當(dāng)然你放到ViewStub中去那么就只會(huì)加載ViewStub并不會(huì)把相對(duì)應(yīng)的View也加載進(jìn)去,所以從而可以起到懶加載的效果。
所以、ViewStub用起來(lái)..