Android中ViewStub原理解析

本文主要從如下幾點(diǎn)來(lái)學(xué)習(xí)ViewStub

  • ViewStub是啥
  • ViewStub的屬性解析
  • ViewStub的代碼實(shí)操
  • ViewStub的原理解析
  • ViewStub實(shí)際中一般常用的情景
  • ViewStub的兩個(gè)小問(wèn)題

ViewStub是啥

在介紹ViewStub是啥之前禽作,我們了解下為什么要用ViewStub

在我們?nèi)粘i_(kāi)發(fā)中,有些布局或者控件一開(kāi)始并不需要顯示群嗤,是根據(jù)業(yè)務(wù)場(chǎng)景來(lái)控制顯示狀態(tài)的总滩,我們通常的做法就是在xml文件設(shè)置不可見(jiàn)蒸甜,然后通過(guò)setVisibility()方法來(lái)更新它的可見(jiàn)性骂铁,但是這樣做會(huì)對(duì)程序的性能有一定的影響,我們知道加載布局的時(shí)候有兩個(gè)瓶頸 一個(gè)是將xml文件加載到內(nèi)存中是IO操作漫雕,通過(guò)反射或者到View的對(duì)象反射操作是耗時(shí)操作滨嘱,這兩者都是耗時(shí)的操作。

基于上面的業(yè)務(wù)情況浸间,出現(xiàn)了ViewStub的標(biāo)簽太雨,它是按需加載View,能夠很容易實(shí)現(xiàn)布局的懶加載來(lái)提升程序的性能发框。

  • ViewStub 繼承于 View

  • 看下源碼聲明

    /*
     * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
     * layout resources at runtime.
     * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
     * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
     * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
     * {@link #inflate()} is invoked.
     */
    
    
    //ViewStub是一個(gè)不可見(jiàn)的躺彬,寬高為0的View,可用于在程序運(yùn)行的時(shí)候延遲  加載布局資源的(用于實(shí)現(xiàn)布局資源的“懶加載”)
    
    //當(dāng)使ViewStub可見(jiàn)或者調(diào)用inflate方法梅惯,可以使布局資源被加載宪拥!
    
    //ViewStub存在于視圖的層級(jí)中直到setVisibility()方法或者inflate()方法被執(zhí)行后,ViewStub相關(guān)的資源就會(huì)被加載并在控件層級(jí)結(jié)構(gòu)中代替ViewStub铣减,同時(shí)ViewStub會(huì)從控件中移除她君。
    
    

ViewStub的屬性解析

android:id
  • ViewStub在布局文件中ID,用于在代碼中訪問(wèn)
  • View共有的
android:layout
  • 在顯示ViewStub時(shí)真正加載并且顯示的布局文件
  • ViewStub特有的
android:inflatedId
  • 真正布局加載后的布局Id
  • ViewStub特有的

ViewStub的代碼實(shí)操

Xml資源文件
  • 代碼如下

    <ViewStub android:id="@+id/stub"       
      android:inflatedId="@+id/subTree"                                        
      android:layout="@layout/mySubTree              
      android:layout_width="120dp"
      android:layout_height="40dp"/>
    
Java代碼
  • 代碼如下

    //1,通過(guò)id找到ViewStub葫哗,得到ViewStub對(duì)象
    ViewStub myViewStub = (ViewStub)findViewById(R.id.stub);
    if(myViewStub!=null){
      //2,通過(guò)inflate方法加載真正的布局View
      View myInflatedView = myViewStub.inflate();
      //3,找到相應(yīng)的控件
     TextView myTextView =  myInflatedView.findViewById(R.id.my_text_view);
    }
    

ViewStub的原理解析

ViewStub的構(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);
            saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
                    defStyleRes);
    
            //獲取在xml文件中定義的inflatedId屬性
            mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
            //獲取到xml文件中定義的layout屬性
            mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
            //獲取xml文件中定義的id屬性
            mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
            a.recycle();
    
            //設(shè)置ViewStub直接不顯示
            //也可以看出來(lái)缔刹,你在xml文件中如何控制它的顯示屬性,都是不顯示的
            setVisibility(GONE);
            // 設(shè)置ViewStub不盡興繪制
            setWillNotDraw(true);
        }
    
ViewStub的onMeasure和onDraw方法
  • 代碼如下

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //設(shè)置寬和高都為0 也就是控件的大小為0
            setMeasuredDimension(0, 0);
        } 
    
    @Override
        public void draw(Canvas canvas) {
            //不進(jìn)行任何繪制
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
        }
    
ViewStub的inflate方法
  • 代碼如下

    public View inflate() {
            //獲取ViewStub在布局文件中的父布局
            final ViewParent viewParent = getParent();
    
            if (viewParent != null && viewParent instanceof ViewGroup) {
                //mLayoutResource 就是屬性 layout指定的真正要加載的布局
                if (mLayoutResource != 0) {
                    final ViewGroup parent = (ViewGroup) viewParent;
                    //把真正要顯示的View布局文件渲染成View對(duì)象并且給返回
                    final View view = inflateViewNoAdd(parent);
                    //將ViewStub從布局文件結(jié)構(gòu)中移除劣针,并且把渲染好的View添加到ViewStub所處的位置校镐。
                    replaceSelfWithView(view, parent);
    
                    mInflatedViewRef = new WeakReference<>(view);
                    if (mInflateListener != null) {
                        //保存當(dāng)前View對(duì)象的弱引用,方便其他地方使用
                        mInflateListener.onInflate(this, view);
                    }
    
                    //返回創(chuàng)建的View對(duì)象
                    return view;
                } else {
                    //當(dāng)我們沒(méi)有為ViewStub指定layut屬性時(shí)捺典,會(huì)走這個(gè)case鸟廓,拋出異常
                    throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
                }
            } else {
                //第一個(gè)調(diào)用ViewStub的inflate方法后,會(huì)把ViewStub從布局文件結(jié)構(gòu)中移除,就是沒(méi)有了ViewGroup了
                // 當(dāng)?shù)诙握{(diào)用ViewStub的inflate方法后引谜,會(huì)走這個(gè)case牍陌,拋出異常。
                throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
            }
        }
    
    private View inflateViewNoAdd(ViewGroup parent) {
            // 獲取到布局的填充器
            final LayoutInflater factory;
            if (mInflater != null) {
                factory = mInflater;
            } else {
                factory = LayoutInflater.from(mContext);
            }
            // 把真正要顯示的布局文件渲染成View對(duì)象
            final View view = factory.inflate(mLayoutResource, parent, false);
    
            //  mInflatedId 對(duì)應(yīng) android:inflatedId 如果指定了就為渲染好的View給設(shè)置進(jìn)去
            if (mInflatedId != NO_ID) {
                view.setId(mInflatedId);
            }
            return view;
        }
    
    private void replaceSelfWithView(View view, ViewGroup parent) {
            // 獲取ViewStub在父布局中所處在的位置
            final int index = parent.indexOfChild(this);
            // 將ViewStub從父布局中移除
            parent.removeViewInLayout(this);
            // 獲取ViewStub的布局參數(shù)
            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
            // 當(dāng)設(shè)置了布局參數(shù)(例如 android:width="50dp",height="50dp")
            if (layoutParams != null) {
                // 將渲染好的View連同ViewStub的布局參數(shù)添加到ViewStub所處的位置
                parent.addView(view, index, layoutParams);
            } else {
                //將渲染好的View添加到ViewStub所處的位置
                parent.addView(view, index);
            }
        }
    
  • 在我看來(lái)inflate方法主要的就是inflateViewNoAdd和replaceSelfWithView方法

  • inflateViewNoAdd:獲取到布局渲染器將真正需要展示的布局文件渲染成View并且給返回员咽。

  • replaceSelfWithView:將ViewStub從布局文件結(jié)構(gòu)中移除毒涧,同時(shí)把渲染好的View添加到ViewStub之前所處的位置

  • 之后把渲染好的View的弱引用給存儲(chǔ)起來(lái)。方便在setVisibility()方法中使用贝室。

ViewStub的setVisibility()方法
  • 代碼如下

    public void setVisibility(int visibility) {
            // 縱觀全局契讲,mInflatedViewRef只有在inflate方法中初始化了,
            // 當(dāng)真正的布局文件被加載之后
            if (mInflatedViewRef != null) {
                // 獲取到當(dāng)前的View
                View view = mInflatedViewRef.get();
                if (view != null) {
                    //操縱當(dāng)前View的可見(jiàn)行
                    view.setVisibility(visibility);
                } else {
                    throw new IllegalStateException("setVisibility called on un-referenced view");
                }
            } else {
                //沒(méi)有調(diào)用inflate的話档玻,會(huì)設(shè)置可見(jiàn)性
                super.setVisibility(visibility);
                //當(dāng) 當(dāng)前設(shè)置可見(jiàn)性為 VISIBLE或者INVISIBLE的時(shí)候怀泊,會(huì)調(diào)用inflate方法。
                if (visibility == VISIBLE || visibility == INVISIBLE) {
                    inflate();
                }
            }
        }
    

ViewStub實(shí)際中一般常用情景

  • 比如我們?cè)跓o(wú)數(shù)據(jù)或者網(wǎng)絡(luò)錯(cuò)粗的時(shí)候误趴,需要單獨(dú)顯示一個(gè)布局,那么這個(gè)布局就可以用ViewStub务傲。

ViewStub的幾個(gè)問(wèn)題

兩次調(diào)用ViewStub的inflate方法會(huì)怎么樣凉当?
  • 我們知道第一次調(diào)用inflate方法的時(shí)候,會(huì)將ViewStub從布局文件結(jié)構(gòu)中移除售葡。
  • 當(dāng)?shù)诙握{(diào)用的時(shí)候看杭,ViewStub已經(jīng)沒(méi)有父控件了,當(dāng)做錯(cuò)誤檢查的時(shí)候挟伙,會(huì)拋出異常楼雹。
在xml文件中為ViewStub設(shè)置了 android:visibility="visible" 屬性,ViewStub中真正要顯示的View會(huì)顯示嘛尖阔?
  • 不會(huì)贮缅,我們?cè)赩iewStub的構(gòu)造方法中,有看到它默認(rèn)調(diào)用了setVisibility(GONE)
  • 所以你在xml文件中如何操作可見(jiàn)行介却,都是沒(méi)法顯示的谴供。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市齿坷,隨后出現(xiàn)的幾起案子桂肌,更是在濱河造成了極大的恐慌,老刑警劉巖永淌,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崎场,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡遂蛀,警方通過(guò)查閱死者的電腦和手機(jī)谭跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人饺蚊,你說(shuō)我怎么就攤上這事萍诱。” “怎么了污呼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵裕坊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我燕酷,道長(zhǎng)籍凝,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任苗缩,我火速辦了婚禮饵蒂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酱讶。我一直安慰自己退盯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布泻肯。 她就那樣靜靜地躺著渊迁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灶挟。 梳的紋絲不亂的頭發(fā)上琉朽,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音稚铣,去河邊找鬼箱叁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惕医,可吹牛的內(nèi)容都是我干的耕漱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼曹锨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孤个!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起沛简,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤齐鲤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后椒楣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體给郊,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年捧灰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淆九。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片统锤。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炭庙,靈堂內(nèi)的尸體忽然破棺而出饲窿,到底是詐尸還是另有隱情,我是刑警寧澤焕蹄,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布逾雄,位于F島的核電站,受9級(jí)特大地震影響腻脏,放射性物質(zhì)發(fā)生泄漏鸦泳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一永品、第九天 我趴在偏房一處隱蔽的房頂上張望做鹰。 院中可真熱鬧,春花似錦鼎姐、人聲如沸钾麸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喂走。三九已至,卻和暖如春谋作,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乎芳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工遵蚜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奈惑。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓吭净,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肴甸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寂殉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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