ViewStub你真的瞭解嗎

目錄介紹

  • 01.什麼是ViewStub
  • 02.ViewStub構造方法
  • 03.inflate()方法解析
  • 04.WeakReference使用
  • 05.ViewStub爲什麼無大小
  • 06.ViewStub爲什麼不繪製
  • 07.能夠屢次inflate()嗎
  • 08.ViewStub不支持merge
  • 09.ViewStub使用場景
  • 10.ViewStub總結分析

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 連接地址:https://github.com/yangchong2...
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!

01.什麼是ViewStub

  • ViewStub 是一個看不見的,沒有大小,不佔佈局位置的 View,能夠用來懶加載佈局。
  • 當 ViewStub 變得可見或 inflate() 的時候,佈局就會被加載(替換 ViewStub)。所以,ViewStub 一直存在於視圖層次結構中直到調用了 setVisibility(int)inflate()
  • 在 ViewStub 加載完成後就會被移除,它所佔用的空間就會被新的佈局替換。

02.ViewStub構造方法

  • 先來看看構造方法:java

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);
    
        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        // 要被加載的佈局 Id
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        // 要被加載的佈局
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        // ViewStub 的 Id
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();
    
        // 初始狀態爲 GONE
        setVisibility(GONE);
        // 設置爲不會繪製
        setWillNotDraw(true);
    }
  • 接下來就看看關鍵的方法,而後看看初始化狀態setVisibility方法。android

    // 複寫了 setVisibility(int) 方法
    @Override
    @android.view.RemotableViewMethod
    public void setVisibility(int visibility) {
        // private WeakReference<View> mInflatedViewRef;
        // mInflatedViewRef 是對佈局的弱引用
        if (mInflatedViewRef != null) {
            // 若是不爲 null,就拿到懶加載的 View
            View 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) {
                // 由於在這裏調用了 inflate()
                inflate();
            }
        }
    }

03.inflate()方法解析

  • 核心來了,平時用的時候,會常常調用到該方法。inflate() 是關鍵的加載實現,代碼以下所示:git

    public View inflate() {
        // 獲取父視圖
        final ViewParent viewParent = getParent();
        
        if (viewParent != null && viewParent instanceof ViewGroup) {
            // 若是沒有指定佈局,就會拋出異常
            if (mLayoutResource != 0) {
                // viewParent 需爲 ViewGroup
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    // 若是沒有指定 LayoutInflater
                    factory = LayoutInflater.from(mContext);
                }
                // 獲取佈局
                final View view = factory.inflate(mLayoutResource, parent,
                        false);
                // 爲 view 設置 Id
                if (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 不爲空
                    // 就設置給 view
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }
                
                // mInflatedViewRef 就是在這裏對 view 進行了弱引用
                mInflatedViewRef = new WeakReference<View>(view);
    
                if (mInflateListener != null) {
                    // 回調
                    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使用特色github

    • ViewStub只能被Inflate一次,inflate以後ViewStub對象就會被置爲空。即某個被ViewStub指定的佈局被Inflate後,就不可以再經過ViewStub來控制它了。
    • ViewStub只能用來Inflate一個佈局文件,而不是某個具體的View,固然也能夠把View寫在某個佈局文件中。

04.WeakReference使用

  • 使用了弱引用管理對象的建立,代碼以下所示面試

    • 在這裏使用了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 {
            
        }
    }
    • 在這裏建立了弱引用對象
    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");
            }
        } 
    }

05.ViewStub爲什麼無大小

  • 首先先看一段源碼,以下所示:canvas

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }
    
    @Override
    public void draw(Canvas canvas) {
    }
    
    @Override
    protected void dispatchDraw(Canvas canvas) {
    }
  • 有沒有以爲很不同凡響markdown

    • draw和dispatchDraw雖然重寫了,可是看代碼卻都是什麼也不作!而且onMeasure還什麼也不作,直接setMeasuredDimension(0,0);來把view區域設置位0,原來一個ViewStub雖然是一個view,倒是一個沒有任何顯示內容,也不顯示任何內容的特殊view,而且對layout在加載時候不可見的。

06.ViewStub爲什麼不繪製

  • 具體看一下setWillNotDraw(true)方法,代碼以下:網絡

    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
  • View中,對於WILL_NOT_DRAW是這樣定義的:app

    /**
     * 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.
*/
static final int WILL_NOT_DRAW = 0x00000080;
```
  • 設置WILL_NOT_DRAW以後,onDraw()不會被調用,經過略過繪製的過程,優化了性能。在ViewGroup中,初始化時設置了WILL_NOT_DRAW,代碼以下:async

    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 default
        if (!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;
    }
  • 因此,在寫自定義佈局時,若是須要調用onDraw()進行繪製,則須要在初始化時候,調用setWillNotDraw(false)。如果想要更進一步閱讀View中WILL_NOT_DRAW的相關源碼,能夠去看下PFLAG_SKIP_DRAW相關的代碼。

07.能夠屢次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 {
                //若是已經加載出來,再次inflate就會拋出異常呢
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
  • 其實也能夠用一張圖來理解它,以下所示,摘自網絡

    • image
  • 也就是說,一旦調用inflate上面的方法後ViewStub就會變成null了,所以使用該對象特別須要注意空指針問題。

08.ViewStub不支持merge

  • 不能引入包含merge標籤的佈局到ViewStub中。不然會報錯:android.view.InflateException: Binary XML file line #1: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true

09.ViewStub使用場景

  • 通常的app中大多有這麼一個功能,當加載的數據爲空時顯示一個數據爲空的視圖、在數據加載失敗時顯示加載失敗對應的UI,當沒有網絡的時候加載沒有網絡的UI,並支持點擊重試會比白屏的用戶體驗更好一些。俗稱,頁面狀態切換管理……通常來講,加載中、加載失敗、空數據等狀態的UI風格,在App內的全部頁面中須要保持一致,也就是須要作到全局統一,也支持局部定製。
  • ViewStub的優點在於在上面的場景中,並不必定須要把全部的內容都展現出來,能夠隱藏一些View視圖,待用戶須要展現的時候再加載到當前的Layout中,這個時候就能夠用到ViewStub這個控件了,這樣能夠減小資源的消耗,使最初的加載速度變快。
  • 那麼就有了以前開發使用的狀態管理器開源庫,就是採用了ViewStub這個控件,讓View狀態的切換和Activity完全分離開。用builder模式來自由的添加須要的狀態View,能夠設置有數據,數據爲空,加載數據錯誤,網絡錯誤,加載中等多種狀態,而且支持自定義狀態的佈局。能夠說徹底不影響性能……

10.ViewStub總結分析

  • 分析源碼的原理,無論認識到哪一步,最終的目標仍是在運用上,即把看源碼得到的知識用到實際開發中,那麼關於ViewStub的使用技巧,具體能夠看個人狀態管理器案例,連接地址:https://github.com/yangchong2...
  • 歡迎你的star,這也是開源和寫博客的源源動力,哈哈

ViewStub狀態管理庫:https://github.com/yangchong2...

開源博客大彙總:https://github.com/yangchong2...

相關文章
相關標籤/搜索