ViewStub源碼解析

閑來無事戏罢,把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);
}

這里面有三個地方要說明一下。

  1. mIDViewStub里面并沒有定義溪窒,mIDView里面定義的坤塞,而且mID訪問修飾符是default,也就是同包下可以訪問澈蚌,而這個mID就是我們經(jīng)常使用的Viewid摹芙,一般在xml中指定,也可以通過ViewsetIdgetId進行操作宛瞄。然而ViewStub并沒有調(diào)用setId方法設(shè)置浮禾,而是很囂張的直接給mID賦值,這是因為ViewStubView在同一個包里坛悉,所以比較牛逼伐厌,如果我們自己寫自定義View可干不了這事,當(dāng)然可以通過反射做裸影。
  2. setVisibility()方法在ViewStub中被重寫挣轨,這個方法比較關(guān)鍵,后面說轩猩。
  3. setWillNotDraw()這個方法View中默認(rèn)是false卷扮,而ViewGroup中默認(rèn)是true,如果為true那么ViewonDraw()不會被調(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顯示出來有兩種方式。

  1. 調(diào)用setVisibility()設(shè)置為VISIBLE或者INVISIBLE
  2. 調(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的源碼就分析完了揩抡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末户侥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子峦嗤,更是在濱河造成了極大的恐慌蕊唐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烁设,死亡現(xiàn)場離奇詭異替梨,居然都是意外死亡,警方通過查閱死者的電腦和手機装黑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門副瀑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曹体,你說我怎么就攤上這事∠趵茫” “怎么了箕别?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我串稀,道長除抛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任母截,我火速辦了婚禮到忽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘清寇。我一直安慰自己喘漏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布华烟。 她就那樣靜靜地躺著翩迈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盔夜。 梳的紋絲不亂的頭發(fā)上负饲,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音喂链,去河邊找鬼返十。 笑死,一個胖子當(dāng)著我的面吹牛椭微,可吹牛的內(nèi)容都是我干的洞坑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼赏表,長吁一口氣:“原來是場噩夢啊……” “哼检诗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓢剿,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤逢慌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后间狂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攻泼,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年鉴象,在試婚紗的時候發(fā)現(xiàn)自己被綠了忙菠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纺弊,死狀恐怖牛欢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淆游,我是刑警寧澤傍睹,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布隔盛,位于F島的核電站,受9級特大地震影響拾稳,放射性物質(zhì)發(fā)生泄漏吮炕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一访得、第九天 我趴在偏房一處隱蔽的房頂上張望龙亲。 院中可真熱鬧,春花似錦悍抑、人聲如沸鳄炉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迎膜。三九已至,卻和暖如春浆兰,著一層夾襖步出監(jiān)牢的瞬間磕仅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工簸呈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榕订,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓蜕便,卻偏偏與公主長得像劫恒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轿腺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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

  • 引言:一個可用于性能優(yōu)化的控件两嘴。時間:2017年09月21日作者:JustDo23Github:https://g...
    JustDo23閱讀 6,265評論 4 5
  • 在開發(fā)過程中,往往會聽到 “性能優(yōu)化” 這個概念族壳,這個概念很大憔辫,比如網(wǎng)絡(luò)性能優(yōu)化、耗電量優(yōu)化等等仿荆,對我們開發(fā)者而言...
    湫水長天閱讀 1,149評論 0 4
  • (1)什么時候使用ViewStub贰您?為什么使用ViewStub? 當(dāng)我們需要根據(jù)某個條件控制某個View的顯示或者...
    CnPeng閱讀 52,655評論 21 84
  • Viewtub源碼分析 A ViewStub is an invisible, zero-sized View t...
    Y小圓臉閱讀 338評論 0 0
  • 前言 關(guān)于view的使用以及列表式加載view的簡單介紹。 View與ViewGroup: View作為一個視圖類...
    Ivor0057閱讀 9,066評論 0 20