DrawerLayout實現(xiàn)雙層Drawer

DrawerLayout是實現(xiàn)側(cè)邊抽屜(Drawer)最方便的方法, 但是它僅僅支持單層Drawer, 這應該是因為在Material Design里面僅提及使用單層Drawer. 不過實際中有些場景需要我們實現(xiàn)雙層Drawer, 例如京東下面的這個功能.

京東篩選面板

關(guān)鍵的交互就是, 打開第一層Drawer后, 點擊其中的按鈕會打開第二層Drawer.

題外話: 雖然這里使用了京東的做示例, 但是京東這里應該不是用雙層DrawerLayout實現(xiàn), 因為第二層Drawer關(guān)閉后不會返回第一層Drawer, 不過也有可能是需求就是這樣.

原生實現(xiàn)的問題

如果要用DrawerLayout實現(xiàn), 當然想到的會是直接添加兩個Gravity.Right的控件了, 不過很快你就會得到一個IllegalStateException異常, 提示

java.lang.IllegalStateException: Child drawer has absolute gravity RIGHT but this DrawerLayout already has a drawer view along that edge
at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1100)
at android.view.View.measure(View.java:18417)
...

看錯誤提示就可以知道

DrawerLayout每個邊緣只能添加一個Drawer.

雙層Drawer解決辦法

源碼分析的過程就不說太多了, 只說關(guān)鍵的地方.
因為存在兩個同側(cè)的Drawer, 所以在遍歷子控件發(fā)現(xiàn)第二個Drawer的時候就會拋出異常.從上面的異常信息就可以知道異常的從DrawerLayout#onMeasure方法中拋出的, 所以要想不報錯

自定義View繼承DrawerLayout并重寫DrawerLayout#onMeasure方法, 處理它拋出的異常.

基本代碼如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    // 雙層Drawer, 原生會拋出異常 
    try { 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    } catch (IllegalStateException e) { 
        // 捕獲異常... 
    }
}

不過這樣app是不錯報錯退出了, 但是因為拋出異常, 所以會導致一些方法沒執(zhí)行, 看DrawerLayout#onMeasure中異常拋出之后的代碼可以知道, 如果沒有拋出異常, 正常獲取到一個Drawer之后, 會調(diào)用child.measure(drawerWidthSpec, drawerHeightSpec)來限定Drawer的寬高, 所以我們捕獲異常之后需要手動做這部分的工作. 因為只會在遍歷到第二個Drawer的時候才會拋出異常, 所以

在捕獲異常時, 手動調(diào)用第二層Drawer的measure方法.

具體的邏輯直接從DrawerLayout#onMeasure復制出來就可以了, 代碼如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    // 雙層Drawer, 原生會拋出異常 
    try { 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    } catch (IllegalStateException e) { 
        // 取第二層Drawer, demo里面最后一個子View就是第二層Drawer 
        final int childCount = getChildCount(); 
        final View child = getChildAt(childCount - 1); 
        // 源碼的默認值是64dp, 為了方便直接寫死 
        final float density = getResources().getDisplayMetrics().density; 
        int minDrawerMargin = (int) (64 * density + 0.5f); 
        // 以下代碼直接取自DrawerLayout#onMeasure 
        final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
        final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, 
                minDrawerMargin + lp.leftMargin + lp.rightMargin, lp.width); 
        final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, 
                lp.topMargin + lp.bottomMargin, lp.height); 
        child.measure(drawerWidthSpec, drawerHeightSpec); 
    }
}

到這里就可以正常顯示雙層Drawer了.

問題

仔細測試之后會發(fā)現(xiàn)有一個小BUG, 第一層Drawer不能通過點擊左側(cè)的陰影來收起Drawer.
具體的debug過程就不說了, 當點擊左側(cè)陰影的時候確實會執(zhí)行收起Drawer的相關(guān)代碼, 但是相關(guān)的動畫卻不會起作用, 具體的原因超出本文范圍就不探究了.

為了解決這個問題自然想到我們自己處理點擊事件.
點擊事件一般在onTouchEvent中處理ACTION_UP事件, 所以查看DrawerLayout#onTouchEvent源碼, 可以知道當接收到ACTION_UP事件時會查找點擊區(qū)域的控件, 如果該控件不是Drawer, 就會調(diào)用closeDrawers()來關(guān)閉Drawer.

所以我們針對上面出現(xiàn)的問題, 專門處理第一層Drawer的關(guān)閉問題. 思路是

接收到ACTION_UP事件后判斷是不是第一層Drawer打開, 第二層Drawer關(guān)閉, 如果是就關(guān)閉第一層Drawer, 其他情況則交給原來的方法處理.

基本的代碼如下:

@Override
public boolean onTouchEvent(MotionEvent ev) { 
    /** 雙層Drawer時, 不能正常通過點擊收起第一層Drawer, 所以在這里自己處理 */ 
    final int action = ev.getAction(); 
    switch (action & MotionEventCompat.ACTION_MASK) { 
        case MotionEvent.ACTION_DOWN: 
            // 記錄坐標參數(shù) 
            break; 
        case MotionEvent.ACTION_UP: { 
            // mRightBelowView為第一層Drawer 
            // mRightAboveView為第二層Drawer 
            final float x = ev.getX(); 
            final float y = ev.getY(); 
            if (x < mRightBelowView.getLeft()) {
                // 判斷點擊的是陰影區(qū)域 
                final float dx = x - mInitialMotionX; 
                final float dy = y - mInitialMotionY; 
                final int slop = mTouchSlop; 
                if (dx * dx + dy * dy < slop * slop) {// 判斷不是滑動 
                    // 當?shù)诙覦rawer沒有打開而第一層Drawer打開時, 收起第一層Drawer 
                    if (!isDrawerOpen(mRightAboveView) &&  isDrawerOpen(mRightBelowView)) {
                        closeDrawer(mRightBelowView); 
                        // 直接返回不執(zhí)行默認代碼 
                        return true; 
                    } 
                } 
            } 
            break; 
        } 
    } 
    // 其他情況使用默認代碼 
    return super.onTouchEvent(ev);
}

這樣就解決點擊關(guān)閉第一層Drawer的問題.

這里有一個小細節(jié), 因為有雙層Drawer, 所以不應該使用openDrawer(@EdgeGravity int gravity)等方法來操作Drawer, 而應該直接指定Drawer控件, 使用openDrawer(View drawerView)等方法.

實際效果如下:

Demo效果

完整的自定義View代碼戳這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌属百,老刑警劉巖斑举,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件先煎,死亡現(xiàn)場離奇詭異,居然都是意外死亡奔滑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門谜喊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倦始,你說我怎么就攤上這事斗遏。” “怎么了鞋邑?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵诵次,是天一觀的道長。 經(jīng)常有香客問我枚碗,道長逾一,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任肮雨,我火速辦了婚禮遵堵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怨规。我一直安慰自己陌宿,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布波丰。 她就那樣靜靜地躺著壳坪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掰烟。 梳的紋絲不亂的頭發(fā)上爽蝴,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音纫骑,去河邊找鬼蝎亚。 笑死,一個胖子當著我的面吹牛惧磺,可吹牛的內(nèi)容都是我干的颖对。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼磨隘,長吁一口氣:“原來是場噩夢啊……” “哼缤底!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起番捂,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤个唧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后设预,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徙歼,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了魄梯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨螺。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酿秸,靈堂內(nèi)的尸體忽然破棺而出灭翔,到底是詐尸還是另有隱情,我是刑警寧澤辣苏,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布肝箱,位于F島的核電站,受9級特大地震影響稀蟋,放射性物質(zhì)發(fā)生泄漏煌张。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一退客、第九天 我趴在偏房一處隱蔽的房頂上張望骏融。 院中可真熱鬧,春花似錦井辜、人聲如沸绎谦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窃肠。三九已至,卻和暖如春刷允,著一層夾襖步出監(jiān)牢的瞬間冤留,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工树灶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纤怒,地道東北人痘煤。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓瓣蛀,卻偏偏與公主長得像故爵,于是被迫代替她去往敵國和親检柬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,848評論 25 707
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程料扰,因...
    小菜c閱讀 6,375評論 0 17
  • 一. 什么是DrawerLayout(抽屜式導航欄) 抽屜式導航欄是一個面板喂江,它將應用的主要導航選項顯示在屏幕左邊...
    NickelFox閱讀 1,772評論 0 6
  • 先對曾經(jīng)點喜歡或者收藏這篇文章的朋友說聲抱歉胆敞,因部分原因個人決定在簡書停更并轉(zhuǎn)移駐扎到其他平臺诺祸。本想刪除賬號携悯,可不...
    OCNYang閱讀 6,443評論 10 84
  • 4月22日 , 小向日葵苗已經(jīng)長高了3厘米左右筷笨,它們生長的速度太快了憔鬼,小盆子已經(jīng)要裝不下它們了龟劲。 4月23日,...
    程諾兒閱讀 406評論 0 3