ViewStub你肯定听过,但是这些细节了解吗?
1什么是ViewStub
1. ViewStub 是一個看不見的,沒有大小,不占布局位置的 View,可以用來懶加載布局。
2. 當 ViewStub 變得可見或 inflate() 的時候,布局就會被加載(替換 ViewStub)。因此,ViewStub 一直存在于視圖層次結構中直到調(diào)用了 setVisibility(int) 或 inflate()。
3. 在?ViewStub 加載完成后就會被移除,它所占用的空間就會被新的布局替換。
在這分享一份整理了2個月的Android進階面試解析筆記文檔,包括了知識點筆記和高頻面試問題解析及部分知識點視頻講解給大家!為了不影響閱讀,在這以圖片展示部分內(nèi)容于目錄截圖,有需要的朋友麻煩點贊后點擊下面在線鏈接獲取免費領取方式吧!
阿里P6P7【安卓】進階資料分享+加薪跳槽必備面試題
2ViewStub構造方法
先來看看構造方法:
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context);final TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ViewStub, defStyleAttr, defStyleRes);// 要被加載的布局 IdmInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);// 要被加載的布局mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);// ViewStub 的 IdmID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);a.recycle();// 初始狀態(tài)為 GONEsetVisibility(GONE);// 設置為不會繪制setWillNotDraw(true); }接下來就看看關鍵的方法,然后看看初始化狀態(tài)setVisibility方法。
// 復寫了 setVisibility(int) 方法 @Override @android.view.RemotableViewMethod public void setVisibility(int visibility) {// private WeakReference<View> mInflatedViewRef;// mInflatedViewRef 是對布局的弱引用if (mInflatedViewRef != null) {// 如果不為 null,就拿到懶加載的 ViewView view = mInflatedViewRef.get();if (view != null) {// 然后就直接對 View 進行 setVisibility 操作view.setVisibility(visibility);} else {// 如果為 null,就拋出異常throw new IllegalStateException("setVisibility called on un-referenced view");}} else {super.setVisibility(visibility);// 之前說過,setVisibility(int) 也可以進行加載布局if (visibility == VISIBLE || visibility == INVISIBLE) {// 因為在這里調(diào)用了 inflate()inflate();}} }3inflate()方法解析
核心來了,平時用的時候,會經(jīng)常調(diào)用到該方法。inflate() 是關鍵的加載實現(xiàn),代碼如下所示:
public View inflate() {// 獲取父視圖final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {// 如果沒有指定布局,就會拋出異常if (mLayoutResource != 0) {// viewParent 需為 ViewGroupfinal ViewGroup parent = (ViewGroup) viewParent;final LayoutInflater factory;if (mInflater != null) {factory = mInflater;} else {// 如果沒有指定 LayoutInflaterfactory = LayoutInflater.from(mContext);}// 獲取布局final View view = factory.inflate(mLayoutResource, parent,false);// 為 view 設置 Idif (mInflatedId != NO_ID) {view.setId(mInflatedId);}// 計算出 ViewStub 在 parent 中的位置final int index = parent.indexOfChild(this);// 把 ViewStub 從 parent 中移除parent.removeViewInLayout(this);// 接下來就是把 view 加到 parent 的 index 位置中final ViewGroup.LayoutParams layoutParams = getLayoutParams();if (layoutParams != null) {// 如果 ViewStub 的 layoutParams 不為空// 就設置給 viewparent.addView(view, index, layoutParams);} else {parent.addView(view, index);}// mInflatedViewRef 就是在這里對 view 進行了弱引用mInflatedViewRef = new WeakReference<View>(view);if (mInflateListener != null) {// 回調(diào)mInflateListener.onInflate(this, 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");} }Inflate使用特點
1. ViewStub只能被Inflate一次,inflate之后ViewStub對象就會被置為空。即某個被ViewStub指定的布局被Inflate后,就不能夠再通過ViewStub來控制它了。
2. ViewStub只能用來Inflate一個布局文件,而不是某個具體的View,當然也可以把View寫在某個布局文件中。
4WeakReference使用
使用了弱引用管理對象的創(chuàng)建,代碼如下所示
在這里使用了get方法
@Override @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync") 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 {} }在這里創(chuàng)建了弱引用對象
public View inflate() {final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {mInflatedViewRef = new WeakReference<>(view);return view;} else {throw new IllegalArgumentException("ViewStub must have a valid layoutResource");}} }5ViewStub為何無大小
首先先看一段源碼,如下所示:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(0, 0); }@Override public void draw(Canvas canvas) { }@Override protected void dispatchDraw(Canvas canvas) { }有沒有覺得很與眾不同
draw和dispatchDraw雖然重寫了,但是看代碼卻都是什么也不做!并且onMeasure還什么也不做,直接setMeasuredDimension(0,0);來把view區(qū)域設置位0,原來一個ViewStub雖然是一個view,卻是一個沒有任何顯示內(nèi)容,也不顯示任何內(nèi)容的特殊view,并且對layout在加載時候不可見的。
6ViewStub為何不繪制
具體看一下setWillNotDraw(true)方法,代碼如下:
public void setWillNotDraw(boolean willNotDraw) {setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }View中,對于WILL_NOT_DRAW是這樣定義的:
/*** This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be* called and further optimizations will be performed. It is okay to have* this flag set and a background. Use with DRAW_MASK when calling setFlags.* {@hide}*/ static final int WILL_NOT_DRAW = 0x00000080;設置WILL_NOT_DRAW之后,onDraw()不會被調(diào)用,通過略過繪制的過程,優(yōu)化了性能。在ViewGroup中,初始化時設置了WILL_NOT_DRAW,代碼如下:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);initViewGroup();initFromAttributes(context, attrs, defStyleAttr, defStyleRes); }private void initViewGroup() {// ViewGroup doesn't draw by defaultif (!debugDraw()) {setFlags(WILL_NOT_DRAW, DRAW_MASK);}mGroupFlags |= FLAG_CLIP_CHILDREN;mGroupFlags |= FLAG_CLIP_TO_PADDING;mGroupFlags |= FLAG_ANIMATION_DONE;mGroupFlags |= FLAG_ANIMATION_CACHE;mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;}setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);mChildren = new View[ARRAY_INITIAL_CAPACITY];mChildrenCount = 0;mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; }所以,在寫自定義布局時,如果需要調(diào)用onDraw()進行繪制,則需要在初始化時候,調(diào)用setWillNotDraw(false)。若是想要更進一步閱讀View中WILL_NOT_DRAW的相關源碼,可以去看下PFLAG_SKIP_DRAW相關的代碼。
7可以多次inflate()嗎
ViewStub對象只可以Inflate一次,之后ViewStub對象會被置為空。同時需要注意的問題是,inflate一個ViewStub對象之后,就不能再inflate它了,否則會報錯:ViewStub must have a non-null ViewGroup viewParent。
其實看一下源碼就很好理解:
public View inflate() {//獲取viewStub的父容器對象final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;//這里是加載布局,并且給它設置id//布局的加載是通過LayoutInflater解析出來的final View view = inflateViewNoAdd(parent);//這行代碼很重要,下面會將到replaceSelfWithView(view, parent);//使用弱引用mInflatedViewRef = new WeakReference<>(view);if (mInflateListener != null) {mInflateListener.onInflate(this, view);}return view;} else {//如果已經(jīng)加載出來,再次inflate就會拋出異常呢throw new IllegalArgumentException("ViewStub must have a valid layoutResource");}} else {throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");} }其實也可以用一張圖來理解它,如下所示,摘自網(wǎng)絡
也就是說,一旦調(diào)用inflate上面的方法后ViewStub就會變成null了,因此使用該對象特別需要注意空指針問題。
8ViewStub不支持merge
不能引入包含merge標簽的布局到ViewStub中。否則會報錯:
android.view.InflateException: Binary XML file line #1:? can be used only with a valid ViewGroup root and attachToRoot=true
9ViewStub使用場景
一般的app中大多有這么一個功能,當加載的數(shù)據(jù)為空時顯示一個數(shù)據(jù)為空的視圖、在數(shù)據(jù)加載失敗時顯示加載失敗對應的UI,當沒有網(wǎng)絡的時候加載沒有網(wǎng)絡的UI,并支持點擊重試會比白屏的用戶體驗更好一些。
俗稱,頁面狀態(tài)切換管理……一般來說,加載中、加載失敗、空數(shù)據(jù)等狀態(tài)的UI風格,在App內(nèi)的所有頁面中需要保持一致,也就是需要做到全局統(tǒng)一,也支持局部定制。
ViewStub的優(yōu)勢在于在上面的場景中,并不一定需要把所有的內(nèi)容都展示出來,可以隱藏一些View視圖,待用戶需要展示的時候再加載到當前的Layout中,這個時候就可以用到ViewStub這個控件了,這樣可以減少資源的消耗,使最初的加載速度變快。
那么就有了之前開發(fā)使用的狀態(tài)管理器開源庫,就是采用了ViewStub這個控件,讓View狀態(tài)的切換和Activity徹底分離開。用builder模式來自由的添加需要的狀態(tài)View,可以設置有數(shù)據(jù),數(shù)據(jù)為空,加載數(shù)據(jù)錯誤,網(wǎng)絡錯誤,加載中等多種狀態(tài),并且支持自定義狀態(tài)的布局??梢哉f完全不影響性能……
10ViewStub總結分析
分析源碼的原理,不管認識到哪一步,最終的目標還是在運用上,即把看源碼獲得的知識用到實際開發(fā)中,
總結
以上是生活随笔為你收集整理的ViewStub你肯定听过,但是这些细节了解吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cucumber+Rest Assure
- 下一篇: 20个常用模拟电路(嵌入式硬件篇)