閑來無事戏罢,把ViewStub
源碼看了一遍,之前只是知道大概原理脚囊,從來沒有把源碼仔細(xì)看過龟糕,代碼很少一會就能看完,只有300行悔耘,而且有一半是注釋讲岁,讀完有些收獲,做個筆記。
環(huán)境
- Android Studio 1.5 Preview 2
- Android SDK 23
解析
先來看看ViewStub
的成員變量缓艳。
private int mInflatedId; // 保存xml指定的inflatedId
private int mLayoutResource; // 保存xml指定的layout
private WeakReference<View> mInflatedViewRef; // 用弱引用持有inflate之后的原始View
private LayoutInflater mInflater; // 這個不多說
private OnInflateListener mInflateListener; // inflate完成的監(jiān)聽器
然后是構(gòu)造方法校摩。
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
// 獲取xml中指定inflatedId屬性,賦值給mInflatedId
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 獲取xml中指定layout屬性阶淘,賦值給layout
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
// 獲取xml中指定的id屬性
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
// 設(shè)置為不可見
setVisibility(GONE);
// 標(biāo)記為不需要繪制衙吩,提高性能
setWillNotDraw(true);
}
這里面有三個地方要說明一下。
-
mID
在ViewStub
里面并沒有定義溪窒,mID
是View
里面定義的坤塞,而且mID
訪問修飾符是default
,也就是同包下可以訪問澈蚌,而這個mID
就是我們經(jīng)常使用的View
的id
摹芙,一般在xml中指定,也可以通過View
的setId
和getId
進行操作宛瞄。然而ViewStub
并沒有調(diào)用setId
方法設(shè)置浮禾,而是很囂張的直接給mID
賦值,這是因為ViewStub
和View
在同一個包里坛悉,所以比較牛逼伐厌,如果我們自己寫自定義View
可干不了這事,當(dāng)然可以通過反射做裸影。 -
setVisibility()
方法在ViewStub
中被重寫挣轨,這個方法比較關(guān)鍵,后面說轩猩。 -
setWillNotDraw()
這個方法View
中默認(rèn)是false
卷扮,而ViewGroup
中默認(rèn)是true
,如果為true
那么View
的onDraw()
不會被調(diào)用可以提高性能均践。
我再來看看ViewStub
重寫一些View
的繪制過程中的方法晤锹。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 不管xml里面設(shè)置的寬高,全部設(shè)置為0
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
我們看到onMeasure
里面把寬高都設(shè)置為了0彤委,而draw
相關(guān)方法都是空實現(xiàn)鞭铆,這也說明了ViewStub
性能好的原因,它自己相當(dāng)于不存在焦影。
OK车遂,我們知道如果想讓ViewStub
中的真實的View
顯示出來有兩種方式。
- 調(diào)用
setVisibility()
設(shè)置為VISIBLE
或者INVISIBLE
- 調(diào)用
inflate()
方法
我們先看第一種方法斯辰。
@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
// 如果已經(jīng)inflate過舶担,那么mInflatedViewRef不為null
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
// 這里是因為弱引用里面的View被回收了,那說明其他地方已經(jīng)沒有對View的引用了
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
// 如果之前沒有inflate過彬呻,并且需要顯示衣陶,那調(diào)用inflate()方法
inflate();
}
}
}
我們發(fā)現(xiàn)setVisibility()
最終也是調(diào)用了inflate()
方法柄瑰,以為第一次mInflatedViewRef
肯定是null
,那繼續(xù)看inflate()
方法的現(xiàn)實剪况。
public View inflate() {
// 獲取ViewStub的parent教沾,ViewStub只是個占位符,最用要把inflate出來的View加到parent里面
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
// mLayoutResource就是xml指定的layout屬性
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
// 使用LayoutInflater把xml中指定的layout加載出來
final View view = factory.inflate(mLayoutResource, parent,
false);
if (mInflatedId != NO_ID) {
// 給原始的View設(shè)置id拯欧,就是xml中指定的inflatedId
view.setId(mInflatedId);
}
// 找到ViewStub在parent里面的位置
final int index = parent.indexOfChild(this);
// 然后把ViewStub從parent里面移除详囤,ViewStub已經(jīng)沒有存在的意義了
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 下面就是把原始View加到parent中
if (layoutParams != null) {
// 把ViewStub上指定的layout參數(shù)給原始View
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
// 創(chuàng)建弱引用持有View
mInflatedViewRef = new WeakReference<View>(view);
// 觸發(fā)infalte完成的監(jiān)聽器,listener可以通過set方法設(shè)置
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
// 返回原始的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
被移除的過程藏姐,調(diào)用的是parent.removeViewInLayout(View)
方法,而我們平時可能會用removeView(View)
比較多该贾,那么這兩個方法有什么區(qū)別的羔杨,直接看源碼就能明白。
public void removeView(View view) {
if (removeViewInternal(view)) {
requestLayout();
invalidate(true);
}
}
public void removeViewInLayout(View view) {
removeViewInternal(view);
}
很明顯杨蛋,removeView(View)
會引發(fā)重新layout
的過程兜材,也就是如果View
已經(jīng)顯示在屏幕上了,如果我們想要移除逞力,那就調(diào)用removeView(View)
從而刷新頁面曙寡,如果View
沒有顯示在屏幕上,調(diào)用removeViewInLayout(View)
性能會好寇荧,避免了不必要的重繪举庶,而ViewStub
中就是一個很好的例子。
到此ViewStub
的源碼就分析完了揩抡。