遊玩 Navigation Drawer 大集合


前言

    導航欄是現在 APP 很常見的功能元件,在 Android UI design guide 中,說到它是用來前往該 APP 其他功能頁或使用上的終結點(像是帳號轉換),在畫面上既可以常駐 ,也可以收在導航按鈕中。既然是現在的主流設計,官方也推出了封裝的類別,能更快速的建構這樣使用習慣的 APP,也能統一該 UI 的風格,於是這次便要來透徹了解在 Android SDK version 22.2.0 加入的 NavigationView

概觀

先從官方的教學文件開始吧!看完文件我們可以在腦中得到下面這個大概的架構圖:


    現在我們知道大致上要怎麼用這個組合,至少有一點概念了,接著要透過了解更多原生實作細節,讓我們更充分的明白自己手上握有的工具,進一步如何客製化。所以現在我們就逐個突破,來看看內部到底是封裝了什麼東西吧。

/* 在創建新專案時可以透過選擇 Navigation Drawer Activity 快速獲得一個寫好的模板 */




DrawerLayout

我們先看看簡單的 DrawerLayout 架構:
(下圖只列出我覺得比較需要關注的部分)



    看 DrawerLayout 的源碼,可以知道它已經實作了拖拉動畫和互動的部分,也就是 ViewDragHelper 的部分,再利用實作 ViewDragCallBack 可以作為跨接溝通,最後封裝完公開出兩個控制函式: openDrawer 、 closeDrawer ,使外部使用變得只需要簡單的指定開/關,再透過傳遞的參數 EdgeGravity 就能找到要做互動的畫面,做完互動了。

EdgeGravity 也就是xml 檔的設定,使用上的配對就是這樣:
  openDrawer(Gravity.START) <-><-> View.getLayoutGravity == Gravity.START

這次的主畫面 xml 檔 : activity_main.xml
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:menu="@menu/activity_main_drawer"/>

</android.support.v4.widget.DrawerLayout>

-> 看看各種拖拉嘗試

    接著我們將重點放在前面也有提到的 DrawerListener,可以看到在 MainActivity onCreate 內直接創建了一個名叫 ActionBarDrawerToggle 的類別,該類別便是實作了介面 DrawerListener,所以直接將它傳給 DrawerLayout,這邊就設定完成了。

如何使用封裝實作後的 ActionBarDrawerToggle :

val toggle = ActionBarDrawerToggle(
                this, drawer_layout, toolbar, 
                R.string.navigation_drawer_open, R.string.navigation_drawer_close)
        drawer_layout.addDrawerListener(toggle)
        toggle.syncState()

    ActionBarDrawerToggle 內部也實作了函式 onOptionsItemSelected,並會由activity 的 onOptionsItemSelected 來驅動,藉此接收到來自 Toolbar 導航按鈕的點擊事件。


onOptionsItemSelected 的實作 :
public boolean onOptionsItemSelected(MenuItem item) {
        if (item != null && item.getItemId() == android.R.id.home && mDrawerIndicatorEnabled) {
            toggle(); // 封裝了選擇 closeDrawer 或 openDrawer 
            return true;
        }
        return false;
    }

還有要注意的就是在 MainActivity 要記得處理返回鍵,點擊時先關閉導航欄。

override fun onBackPressed() {
        if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
            drawer_layout.closeDrawer(GravityCompat.START)
        } else {
            super.onBackPressed()
        }
    }

NavigationView

一樣我們先看看簡單的 NavigationView 架構:


    這邊可以看到 NavigationView 的父類別有 FrameLayout 可以猜到其實這個 View 就是官方刻好的畫面,Menu 物件來組成,NavigationMenu 便是繼承實作 Menu 定義的介面的類別,Menu 介面主要描述了選單物件,需要能被增加刪減,然後能群組選項與基本的查找選項畫面。

    繪製畫面的部分,需要透過 MenuInflater 在創建時 inflate MenuItem,再綁回 NavigationMenu。關於怎們操作物件互動我們可以先看從 NavigationMenu 繼承的 MenuBuilder 找端倪,就發現了 Presenter 的蹤影了。'


加入Presenter的部分與真的初始化 Menu : 

public void addMenuPresenter(MenuPresenter presenter, Context menuContext) {
        mPresenters.add(new WeakReference<MenuPresenter>(presenter));
        presenter.initForMenu(menuContext, this);
        mIsActionItemsStale = true;
    }

先是用 WeakReference 的方式指向被傳進來的 Presenter,保留物件仍能被自動回收的參照,接著請 Presenter 執行選單初始化,所以我們現在可以正式來看 NavigationMenuPresenter 了。

這邊還是稍微畫下類別關係圖好了:



    NavigationMenuView 本身繼承了 RecyclerView,所以這其實就是一個有 Header 的 Recycler 畫面,並在 NavigationMenuAdapter 裡轉接了 ArrayList,而它的佈局設置也已經寫好了,這也使得客製化程度降低。這邊基本上只能透過程式動態修改了。


<android.support.design.internal.NavigationMenuItemView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="?attr/listPreferredItemHeightSmall"
        android:paddingLeft="?attr/listPreferredItemPaddingLeft"
        android:paddingRight="?attr/listPreferredItemPaddingRight"
        android:foreground="?attr/selectableItemBackground"
        android:focusable="true"/>

    而這邊用到的 CallBack 是在 MenuPresenter 定義的介面,能夠監聽 onCloseMenu / onOpenSubMenu。

    最後就是公開的 OnNavigationItemSelectedListener 介面,它能去監聽擁有的 MenuItem 點擊事件,在範例裡,是直接由 MainActivity 實作了。

更多:自己刻 NavigationView 可以多 Free?


整合

合起來後,我們可以看到整個簡單的架構如下圖,元件彼此間的溝通跟關係就非常明瞭了!



終於寫完了 我要來廢話幾句啦 這篇通篇都太嚴肅了吧
通通都用大寫來表達我的幽默感 (笑

留言

熱門文章