Snackbar涮母,你這是怎么了?

起因

Snackbar相信大家都不陌生躁愿,Material Design樣式的消息通知叛本,簡(jiǎn)潔的使用方式,相信很多人都已經(jīng)替換掉Toast彤钟,改投Snackbar了来候。但就是這簡(jiǎn)簡(jiǎn)單單的一句代碼:

Snackbar.make(view, "This is the snackbar.", Snackbar.LENGTH_SHORT).show();

最近卻讓我焦頭爛額。到底是怎么回事呢逸雹?我這里寫(xiě)了個(gè)Demo來(lái)重現(xiàn)一些當(dāng)時(shí)的場(chǎng)景:

    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    ViewGroup root = new RelativeLayout(MainActivity.this);
    root.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
    params.type = WindowManager.LayoutParams.TYPE_PHONE;
    params.format = PixelFormat.RGBA_8888;
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    params.gravity = Gravity.START | Gravity.TOP;
    params.width = 400;
    params.height = 300;
    params.x = 70;
    params.y = 300;
    windowManager.addView(root, params);
    Snackbar.make(root, "This is the snackbar.", Snackbar.LENGTH_SHORT).show();

代碼很簡(jiǎn)單营搅,就是在一個(gè)懸浮框里顯示一個(gè)Sanckbar云挟,本想著平時(shí)經(jīng)常使用的Snackbar在這也不會(huì)出什么問(wèn)題,可現(xiàn)實(shí)卻給了我重重的一擊:

NullPointerException

這是怎么回事转质?平時(shí)在Activity用的好好的Snackbar怎么一到WindowManager就不行了呢园欣?

問(wèn)題的源頭

既然報(bào)錯(cuò)了,那我們先到NullPointerException的源頭看它一看:

  private Snackbar(ViewGroup parent) {
        mTargetParent = parent;
        mContext = parent.getContext();  //就是這里報(bào)的NullPointerException

        ThemeUtils.checkAppCompatTheme(mContext);

        LayoutInflater inflater = LayoutInflater.from(mContext);
        mView = (SnackbarLayout) inflater.inflate(
                R.layout.design_layout_snackbar, mTargetParent, false);

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    }

看到這里我更迷惑了休蟹,這里的parent按道理應(yīng)該是我們傳入的view沸枯,怎么可能為空呢?難道這里的parent另有其人赂弓?看來(lái)我們還是得進(jìn)入Snack.make()方法一探究竟:

    public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
            @Duration int duration) {
        Snackbar snackbar = new Snackbar(findSuitableParent(view));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }

可以看到绑榴,我們傳入的view是先經(jīng)過(guò)一個(gè)findSuitableParent()方法調(diào)用的,聽(tīng)名字就知道肯定是這個(gè)方法搗的鬼盈魁。我們看看這個(gè)findSuitableParent()到底做了什么:

    private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) {
                // We've found a CoordinatorLayout, use it
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) {
                if (view.getId() == android.R.id.content) {
                    // If we've hit the decor content view, then we didn't find a CoL in the
                    // hierarchy, so use it.
                    return (ViewGroup) view;
                } else {
                    // It's not the content view but we'll use it as our fallback
                    fallback = (ViewGroup) view;
                }
            }

            if (view != null) {
                // Else, we will loop and crawl up the view hierarchy and try to find a parent
                final ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);

        // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
        return fallback;
    }

方法的邏輯很簡(jiǎn)單翔怎,就是循環(huán)遍歷view的父視圖,如果某個(gè)父視圖是CoordinatorLayout或者已經(jīng)追溯到了Activity的content視圖就直接返回备埃,查找期間如果某個(gè)view是FrameLayout就將其設(shè)為fallback姓惑,作為備用褐奴,當(dāng)查找到視圖頂端還沒(méi)有找到合適的ViewGroup時(shí)就返回fallback變量按脚。

看了這段代碼,再看看我們之前傳入的view敦冬,整個(gè)視圖層級(jí)里辅搬,既沒(méi)有CoordinatorLayout,也沒(méi)有FrameLayout脖旱,又因?yàn)槭侵苯邮褂肳indowManager顯示的堪遂,所以也沒(méi)有content視圖,真是要啥啥沒(méi)有萌庆,自然最后的fallback為null溶褪,導(dǎo)致了后面的NullPointerException。

Activity的視圖層級(jí)
直接使用WindowManger的視圖層級(jí)

解決方案

相信看到這大家都明白了践险,我們平時(shí)在Activity里不管什么視圖都可以使用Snackbar猿妈,是因?yàn)锳ctivity本身有content視圖可以給Snackbar使用,所以就算我們本身的視圖層級(jí)里沒(méi)有CoordinatorLayout或者FrameLayout巍虫,Snackbar也是可以正常使用的彭则。但這在WindowManager中就不好使了,為保證Snackbar的正常使用占遥,我們的視圖層級(jí)必須包含CoordinatorLayout或者FrameLayout俯抖。這里我選擇使用FrameLayout作為根視圖,其他代碼不變:

ViewGroup root = new FrameLayout(MainActivity.this);

果然這么一改瓦胎,Snackbar可以正常顯示了:

正常運(yùn)行

進(jìn)一步探究

代碼雖然成功運(yùn)行了芬萍,可我還是有些疑惑尤揣,為什么Snackbar必須要使用CoordinatorLayout或者FrameLayout,其他的ViewGroup怎么就入不了Snackbar的法眼呢担忧?想到這清笨,我覺(jué)得我有必要繼續(xù)深究藏在Snackbar身后的秘密过蹂。

你Snackbar不允許我使用其他的ViewGroup,我倒要看看我用一用會(huì)怎么樣!

當(dāng)然暴备,這里普通的調(diào)用是沒(méi)法做到的,會(huì)直接報(bào)NullPointerException霉颠,我們必須采取一些小手段:

    Constructor<Snackbar> snackbarConstructor = Snackbar.class
        .getDeclaredConstructor(ViewGroup.class);
    snackbarConstructor.setAccessible(true);
    Snackbar snackbar = snackbarConstructor.newInstance(root);
    snackbar.setText("This is the snackbar.");
    snackbar.setDuration(Snackbar.LENGTH_SHORT);
    snackbar.show();

這里我用反射直接構(gòu)造一個(gè)Snackbar猬仁,將我們剛才的RelativeLayout根視圖傳入構(gòu)造器。

運(yùn)行轧房!

Snackbar顯示出錯(cuò)

原來(lái)如此拌阴,看來(lái)Snackbar的視圖的布局一定用了一些只有CoordinatorLayout和FrameLayout支持的屬性,如果使用其他的ViewGroup奶镶,Snackbar的顯示就會(huì)出現(xiàn)錯(cuò)誤迟赃。那我們?cè)倏纯碨nackbar的xml文件到底寫(xiě)了些什么:

<view xmlns:android="http://schemas.android.com/apk/res/android"
      class="android.support.design.widget.Snackbar$SnackbarLayout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom"
      android:theme="@style/ThemeOverlay.AppCompat.Dark"
      style="@style/Widget.Design.Snackbar" />

可以看到這里Snackbar使用了android:layout_gravity="bottom"來(lái)保證Snackbar顯示在視圖底部,而這個(gè)layout_gravity屬性只有CoordinatorLayout和FrameLayout支持厂镇,這就是為什么Snackbar不能使用其他ViewGroup的原因纤壁。

另一個(gè)問(wèn)題

這個(gè)問(wèn)題是在我解決上面的問(wèn)題之后出現(xiàn)的:

IllegalArgumentException

其實(shí)原因很簡(jiǎn)單,大家可以翻到前面再看一下Snackbar的構(gòu)造方法捺信,里面有一句:

ThemeUtils.checkAppCompatTheme(mContext);

這一句是檢查Context是否使用的AppCompat主題酌媒,如果不是就會(huì)拋出IllegalArgumentException,因?yàn)楫?dāng)時(shí)是在Service里面創(chuàng)建的視圖迄靠,root視圖的Context自然也是用的Service秒咨,而Service是沒(méi)有Theme的,于是就產(chǎn)生了這個(gè)異常掌挚。解決的方法也很簡(jiǎn)單:

ContextThemeWrapper wrapper = new ContextThemeWrapper(serviceContext, R.style.Theme_AppCompat);
ViewGroup root = new RelativeLayout(wrapper);

那為什么Snackbar一定要求AppCompat主題呢雨席?其實(shí)也是因?yàn)閤ml文件內(nèi)部使用了AppCompat的屬性,我這里就不再展示了吠式,如果感興趣陡厘,可以自行查看。

結(jié)語(yǔ)

其實(shí)解決掉這個(gè)問(wèn)題之后再回頭看一看Snackbar的API介紹奇徒,解釋的也還算清楚:

Snackbar API介紹

但知其然雏亚,也要知其所以然,這樣才算真正的弄知識(shí)摩钙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罢低,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌网持,老刑警劉巖宜岛,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異功舀,居然都是意外死亡萍倡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)辟汰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)列敲,“玉大人,你說(shuō)我怎么就攤上這事帖汞〈鞫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵翩蘸,是天一觀的道長(zhǎng)所意。 經(jīng)常有香客問(wèn)我,道長(zhǎng)催首,這世上最難降的妖魔是什么扶踊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮郎任,結(jié)果婚禮上秧耗,老公的妹妹穿的比我還像新娘。我一直安慰自己涝滴,他們只是感情好绣版,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布胶台。 她就那樣靜靜地躺著歼疮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诈唬。 梳的紋絲不亂的頭發(fā)上韩脏,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音铸磅,去河邊找鬼赡矢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛阅仔,可吹牛的內(nèi)容都是我干的吹散。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼八酒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼空民!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤界轩,失蹤者是張志新(化名)和其女友劉穎画饥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體浊猾,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抖甘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葫慎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衔彻。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖偷办,靈堂內(nèi)的尸體忽然破棺而出米奸,到底是詐尸還是另有隱情,我是刑警寧澤爽篷,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布悴晰,位于F島的核電站,受9級(jí)特大地震影響逐工,放射性物質(zhì)發(fā)生泄漏铡溪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一泪喊、第九天 我趴在偏房一處隱蔽的房頂上張望棕硫。 院中可真熱鬧,春花似錦袒啼、人聲如沸哈扮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滑肉。三九已至,卻和暖如春摘仅,著一層夾襖步出監(jiān)牢的瞬間靶庙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工娃属, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留六荒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓矾端,卻偏偏與公主長(zhǎng)得像掏击,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秩铆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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