ViewStub你真的了解嗎

目錄介紹

  • 01.什么是ViewStub
  • 02.ViewStub構(gòu)造方法
  • 03.inflate()方法解析
  • 04.WeakReference使用
  • 05.ViewStub為何無大小
  • 06.ViewStub為何不繪制
  • 07.可以多次inflate()嗎
  • 08.ViewStub不支持merge
  • 09.ViewStub使用場景
  • 10.ViewStub總結(jié)分析

好消息

  • 博客筆記大匯總【16年3月到至今】狗超,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等港准,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正峦甩,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客搬设,從12年起穴店,積累共計(jì)N篇[近100萬字,陸續(xù)搬到網(wǎng)上]拿穴,轉(zhuǎn)載請注明出處泣洞,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好默色,可以star一下球凰,謝謝!當(dāng)然也歡迎提出建議腿宰,萬事起于忽微呕诉,量變引起質(zhì)變!

01.什么是ViewStub

  • ViewStub 是一個(gè)看不見的吃度,沒有大小甩挫,不占布局位置的 View,可以用來懶加載布局椿每。
  • 當(dāng) ViewStub 變得可見或 inflate() 的時(shí)候伊者,布局就會(huì)被加載(替換 ViewStub)。因此间护,ViewStub 一直存在于視圖層次結(jié)構(gòu)中直到調(diào)用了 setVisibility(int)inflate()亦渗。
  • 在 ViewStub 加載完成后就會(huì)被移除,它所占用的空間就會(huì)被新的布局替換汁尺。

02.ViewStub構(gòu)造方法

  • 先來看看構(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);
        // 要被加載的布局 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();
    
        // 初始狀態(tài)為 GONE
        setVisibility(GONE);
        // 設(shè)置為不會(huì)繪制
        setWillNotDraw(true);
    }
    
  • 接下來就看看關(guān)鍵的方法法精,然后看看初始化狀態(tài)setVisibility方法。
    // 復(fù)寫了 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 進(jìn)行 setVisibility 操作
                view.setVisibility(visibility);
            } else {
                // 如果為 null,就拋出異常
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            // 之前說過搂蜓,setVisibility(int) 也可以進(jìn)行加載布局
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                // 因?yàn)樵谶@里調(diào)用了 inflate()
                inflate();
            }
        }
    }
    

03.inflate()方法解析

  • 核心來了狼荞,平時(shí)用的時(shí)候,會(huì)經(jīng)常調(diào)用到該方法帮碰。inflate() 是關(guān)鍵的加載實(shí)現(xiàn)粘秆,代碼如下所示:
    public View inflate() {
        // 獲取父視圖
        final ViewParent viewParent = getParent();
        
        if (viewParent != null && viewParent instanceof ViewGroup) {
            // 如果沒有指定布局,就會(huì)拋出異常
            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 設(shè)置 Id
                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId);
                }
                // 計(jì)算出 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 不為空
                    // 就設(shè)置給 view
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }
                
                // mInflatedViewRef 就是在這里對 view 進(jìn)行了弱引用
                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使用特點(diǎn)
    • ViewStub只能被Inflate一次收毫,inflate之后ViewStub對象就會(huì)被置為空。即某個(gè)被ViewStub指定的布局被Inflate后殷勘,就不能夠再通過ViewStub來控制它了此再。
    • ViewStub只能用來Inflate一個(gè)布局文件,而不是某個(gè)具體的View玲销,當(dāng)然也可以把View寫在某個(gè)布局文件中输拇。

04.WeakReference使用

  • 使用了弱引用管理對象的創(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");
            }
        } 
    }
    

05.ViewStub為何無大小

  • 首先先看一段源碼贤斜,如下所示:
    @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ū)域設(shè)置位0,原來一個(gè)ViewStub雖然是一個(gè)view,卻是一個(gè)沒有任何顯示內(nèi)容,也不顯示任何內(nèi)容的特殊view瘩绒,并且對layout在加載時(shí)候不可見的猴抹。

06.ViewStub為何不繪制

  • 具體看一下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;
    
  • 設(shè)置WILL_NOT_DRAW之后蟀给,onDraw()不會(huì)被調(diào)用,通過略過繪制的過程阳堕,優(yōu)化了性能跋理。在ViewGroup中,初始化時(shí)設(shè)置了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 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;
    }
    
  • 所以前普,在寫自定義布局時(shí),如果需要調(diào)用onDraw()進(jìn)行繪制壹堰,則需要在初始化時(shí)候拭卿,調(diào)用setWillNotDraw(false)。若是想要更進(jìn)一步閱讀View中WILL_NOT_DRAW的相關(guān)源碼缀旁,可以去看下PFLAG_SKIP_DRAW相關(guān)的代碼记劈。

07.可以多次inflate()嗎

  • ViewStub對象只可以Inflate一次,之后ViewStub對象會(huì)被置為空并巍。同時(shí)需要注意的問題是目木,inflate一個(gè)ViewStub對象之后,就不能再inflate它了,否則會(huì)報(bào)錯(cuò):ViewStub must have a non-null ViewGroup viewParent刽射。军拟。
  • 其實(shí)看一下源碼就很好理解:
    public View inflate() {
        //獲取viewStub的父容器對象
        final ViewParent viewParent = getParent();
    
        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                //這里是加載布局,并且給它設(shè)置id
                //布局的加載是通過LayoutInflater解析出來的
                final View view = inflateViewNoAdd(parent);
                //這行代碼很重要誓禁,下面會(huì)將到
                replaceSelfWithView(view, parent);
    
                //使用弱引用
                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }
                return view;
            } else {
                //如果已經(jīng)加載出來懈息,再次inflate就會(huì)拋出異常呢
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
    
  • 其實(shí)也可以用一張圖來理解它,如下所示摹恰,摘自網(wǎng)絡(luò)
    • image
  • 也就是說辫继,一旦調(diào)用inflate上面的方法后ViewStub就會(huì)變成null了,因此使用該對象特別需要注意空指針問題俗慈。

08.ViewStub不支持merge

  • 不能引入包含merge標(biāo)簽的布局到ViewStub中姑宽。否則會(huì)報(bào)錯(cuò):android.view.InflateException: Binary XML file line #1: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true

09.ViewStub使用場景

  • 一般的app中大多有這么一個(gè)功能,當(dāng)加載的數(shù)據(jù)為空時(shí)顯示一個(gè)數(shù)據(jù)為空的視圖闺阱、在數(shù)據(jù)加載失敗時(shí)顯示加載失敗對應(yīng)的UI炮车,當(dāng)沒有網(wǎng)絡(luò)的時(shí)候加載沒有網(wǎng)絡(luò)的UI,并支持點(diǎn)擊重試會(huì)比白屏的用戶體驗(yàn)更好一些酣溃。俗稱瘦穆,頁面狀態(tài)切換管理……一般來說,加載中赊豌、加載失敗扛或、空數(shù)據(jù)等狀態(tài)的UI風(fēng)格,在App內(nèi)的所有頁面中需要保持一致碘饼,也就是需要做到全局統(tǒng)一告喊,也支持局部定制。
  • ViewStub的優(yōu)勢在于在上面的場景中派昧,并不一定需要把所有的內(nèi)容都展示出來黔姜,可以隱藏一些View視圖,待用戶需要展示的時(shí)候再加載到當(dāng)前的Layout中蒂萎,這個(gè)時(shí)候就可以用到ViewStub這個(gè)控件了秆吵,這樣可以減少資源的消耗,使最初的加載速度變快五慈。
  • 那么就有了之前開發(fā)使用的狀態(tài)管理器開源庫纳寂,就是采用了ViewStub這個(gè)控件,讓View狀態(tài)的切換和Activity徹底分離開泻拦。用builder模式來自由的添加需要的狀態(tài)View毙芜,可以設(shè)置有數(shù)據(jù),數(shù)據(jù)為空争拐,加載數(shù)據(jù)錯(cuò)誤腋粥,網(wǎng)絡(luò)錯(cuò)誤,加載中等多種狀態(tài),并且支持自定義狀態(tài)的布局隘冲∧智疲可以說完全不影響性能……

10.ViewStub總結(jié)分析

  • 分析源碼的原理,不管認(rèn)識(shí)到哪一步展辞,最終的目標(biāo)還是在運(yùn)用上奥邮,即把看源碼獲得的知識(shí)用到實(shí)際開發(fā)中,那么關(guān)于ViewStub的使用技巧罗珍,具體可以看我的狀態(tài)管理器案例洽腺,鏈接地址:https://github.com/yangchong211/YCStateLayout
  • 歡迎你的star,這也是開源和寫博客的源源動(dòng)力覆旱,哈哈

ViewStub狀態(tài)管理庫:https://github.com/yangchong211/YCStateLayout

開源博客大匯總:https://github.com/yangchong211/YCBlogs

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末已脓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子通殃,更是在濱河造成了極大的恐慌,老刑警劉巖厕宗,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件画舌,死亡現(xiàn)場離奇詭異,居然都是意外死亡已慢,警方通過查閱死者的電腦和手機(jī)曲聂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑惠,“玉大人朋腋,你說我怎么就攤上這事∧た” “怎么了旭咽?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赌厅。 經(jīng)常有香客問我穷绵,道長,這世上最難降的妖魔是什么特愿? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任仲墨,我火速辦了婚禮,結(jié)果婚禮上揍障,老公的妹妹穿的比我還像新娘目养。我一直安慰自己,他們只是感情好毒嫡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布癌蚁。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匈勋。 梳的紋絲不亂的頭發(fā)上礼旅,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音洽洁,去河邊找鬼痘系。 笑死,一個(gè)胖子當(dāng)著我的面吹牛饿自,可吹牛的內(nèi)容都是我干的汰翠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼昭雌,長吁一口氣:“原來是場噩夢啊……” “哼复唤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烛卧,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤佛纫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后总放,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呈宇,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年局雄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甥啄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炬搭,死狀恐怖蜈漓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宫盔,我是刑警寧澤融虽,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站灼芭,受9級特大地震影響衣形,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姿鸿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一谆吴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苛预,春花似錦句狼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胳螟。三九已至,卻和暖如春筹吐,著一層夾襖步出監(jiān)牢的瞬間糖耸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工丘薛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘉竟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓洋侨,卻偏偏與公主長得像舍扰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子希坚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容