Android – Toolbar 上的 Navigation Drawer

ToolBar的基础知识请先阅读:android:ToolBar详解(手把手教程)

在 Material Design 发布后,Google 也开始陆续更新了 Google app 的界面,让大家有个范例可以看。而过去大力推动的 actionbar 自然而然也成了众开发者观注的部份;其中的 up button(返回上一级) 的设置在前一篇所介绍的 Toolbar 也已看到。这边还未提到的一个部份是 material design 中有提到的人机交互效果,简言之,就是让使用者明显地感受到在操作 app 时,可以获得明显的回应,从而得到丰富地操作体验感;因此,在刚开始放出的几支 Google app 里,大家一定都有留意到开启 navigation drawer 时,up button 的旋转动画效果。

先来看看效果:

相信大家也都会很好奇这个效果要如何实现,会不会很麻烦?所幸,这个效果,被放进 support v7 之中,我们只要拿來用即可。在本篇中将分下列几个部份,来带大家轻松实现这种效果来。

本篇所使用到的程式码,请到 Github 下载。

1. 实作

我们马上从 navdrawer_deom_checkpoint0 开始 (这份代码其实在 android:ToolBar详解(手把手教程) 一文的自定义颜色一节完成的 toolbar_demo_checkpoint2),

在 activity_main.xml 中加入 DrawerLayout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity">
                                                                                                                                                                                                                                                                             
  <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    ... />
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
  <android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:layout_below="@+id/toolbar">
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    <!-- Content -->
    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
      ...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    </RelativeLayout>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    <!-- Side Drawer -->
    <LinearLayout
      android:id="@+id/drawer_view"
      android:layout_width="@dimen/navdrawer_width"
      android:layout_height="match_parent"
      android:layout_gravity="start"
      android:background="#88FFFFFF"
      android:orientation="vertical">
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    </LinearLayout>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
  </android.support.v4.widget.DrawerLayout>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
</RelativeLayout>

加入后,请记得将原本在 Toolbar 控件之前的 TextView 放到  部份,做 DrawerLayout 其中的內容界面,否则在忘记放入 Content 界面的状況下,关闭侧边栏时,会发生「java.lang.NullPointerException: Attempt to invoke virtual method ‘android.view.ViewGroup$LayoutParams android.view.View.getLayoutParams()’ on a null object reference

这样的错误讯息。

在 drawer_view  中,要记得 layout_gravity 设定成 start  或是 left 。

activity_main.xml 完整程式码请见 github。

再来,就是到 MainActivity.java 中去实作 DrawerLayout,部份程式码如下:

private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
  // 打開 up button
  getSupportActionBar().setDisplayHomeAsUpEnabled(true);
  mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer);
  // 實作 drawer toggle 並放入 toolbar
  mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close);
  mDrawerToggle.syncState();
  mDrawerLayout.setDrawerListener(mDrawerToggle);
}

完成后,就可以看到如上方影像一样的效果了!

完整程式码请见:navdrawer_demo_checkpoint1

2. Side Drawer

一开始,Material Design 设计文档   中,其实是没有看到侧边栏的相关说明,一直到 2014.10 才以「Side Nav」之名,加述于导览文件之中。而这个定义中的 Side Nav 画面,其实跟原本在 actionbar 的阶段是不太一样的,drawer layout 会压在 toolbar 上,如下图:

NavDrawer over toolbar

目前在 Google app 的界面中,还是以前一种为多,而现下 (2014.10) 如同 material  design 在 side nav 呈现的界面还不多。

接下来就用 navdrawer_demo_checkpoint1 做为这个阶段的开始往下进行,需要调整就只有界面 (activity_main.xml) 的部份:

<android.support.v4.widget.DrawerLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/drawer"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  tools:context=".MainActivity">
  <!-- Content -->
  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      ... />
    ...
  </RelativeLayout>
  <!-- Side Drawer -->
  <LinearLayout
    android:id="@+id/drawer_view"
    android:layout_width="@dimen/navdrawer_width"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:clickable="true"
    android:background="#88FFFFFF"
    android:orientation="vertical">
  </LinearLayout>
</android.support.v4.widget.DrawerLayout>

将 DrawerLayout 改到这个 layout 的 root 层,并将 toolbar 移到 content 的 layout 中,这样就可以达到这样的效果了。
需要注意的是地方在 Side 的 layout 要设定 clickable 的属性设定为 true,否则会在侧边栏打开的状况下,还能按到位于界面下方的 up button。
But!人生就是这个 but!

在前一阵子,Google 的设计师为 Google I/O 2014 设计了另外一种样式出来,差异请见下图:

NavDrawer

嗯,简言之,状态栏是半透明的状态,而侧边栏可以被看到。要调整的地方有二,

一在 layout – activity_main.xml:

<android.support.v4.widget.DrawerLayout
  ...
  android:fitsSystemWindows="true"
  ...>
  <!-- Content -->
  ...
  <!-- Side Drawer -->
  <LinearLayout
    ...
    android:fitsSystemWindows="true"
    ... >
  </LinearLayout>
</android.support.v4.widget.DrawerLayout>

在 root 层的 drawer layout 跟 side drawer 的 layout 各别加上 android:fitsSystemWindows="true" 这个属性
另外一个要改的地方是在 v21/styles.xml

<style name="AppTheme" parent="AppTheme.Base">
  ...
  <!--Status bar color-->
  <item name="android:statusBarColor">#88009688</item>
</style>

加入一个有透明度的色码给 android:statusBarColor 这个状态列颜色设定属性。

这样就可以完成这个阶段的程式了。

完整程式码请见:navdrawer_demo_checkpoint2

3. 其他议题

Navigation icon 切换的效果,除了旋转以外,还有另外一种效果,请看下方的影像:

3.1 Navigation icon effect

左上角的切换效果变成线条的的聚合,同时 icon 的颜色也被改为黑色,这个设定只要在 /res/values/styles.xml 做如下设定

<style name="AppTheme.Base" parent="Theme.AppCompat">
                                                                                                                                                                                                                                                                             
  ...
  <!--將新加入的風格 AppTheme.MyDrawerStyle 設定給 drawerArrowStyle 這個屬性-->
  <item name="drawerArrowStyle">@style/AppTheme.MyDrawerArrowStyle</item>
</style>
<!--加入一個新的 navigation drarwer 的風格-->
<style name="AppTheme.MyDrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
  <!--將 spinBars 屬性設定為 false-->
  <item name="spinBars">false</item>
  <!--設定 drawer arrow 的顏色-->
  <item name="color">@android:color/black</item>
</style>

先加入针对 navigation icon 设定的风格: AppTheme.MyDrawerArrowStyle ,其继承自 Widget.AppCompat.DrawerArrowToggle 这个风格。

以本范例来说,这个风格裡设定了两个属性:

   spinBars
旋转效果
false。由此可之,其预设的旋转效果预设值就是 true 了。
color
设定 navigation icon 的颜色。

增加后,再回到 AppTheme.Base 风格中,将此风格设定给 drawerArrowStyle 即可看到效果了。

3.2 Shadows

在影像中,大家应该可以看到,这个范例中还有一处跟之前不同了,内容为 Hello world! 的 TextView 变成黑色了!哦!当然不是要为各位介绍这个以前就有属性。要请各位留意的是在 TextView 周围有一圈淡淡的影子,还有在 Toolbar 也因为下方的影子,看起来较有立体感。而这也就是在 Material design 中有提到的其中一个部份:Shadows

在过去这个效果需要自己去设定 drawable,现在只要设定个属性即可。请到 activity_main.xml 中

<!-- Content -->
<RelativeLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <android.support.v7.widget.Toolbar
    ...
    android:elevation="10dp"
    ...  />
  <TextView
    ...
    android:elevation="10dp"
    android:background="@android:color/black"
    .../>
</RelativeLayout>

最主要就是针对 android:elevation 这个属性设定,而这个属性的意义也如同其字面的意义,他是在承述相较于底层的高度而被创造出来的阴影。而另外,在这段程式码中,特别将 TextView 中的 android:background 也列出来的原因,就是要让大家知道,android:elevation 这个属性要能够生效,该介面元件的背景属性也需要设定哦!

完整程式码请见:navdrawer_demo_checkpoint3

4. 一点想法

新的 navigation drawer 在 material design 的 side nav 裡有著跟过去不同的样式,而在 Google app 中,包含文中提到那种新的呈现样式算来,共有三重风格。这应该也意谓著,以 Google 在 side nav 在 android app 的呈现上,给予了这些弹性在。换句话说,这样看来,目前看到的这三种样式在 android app 上来看,都可以算是 material design。

也许有人会问,那导览文件是写好玩的吗?以个人的观点来看,那个的确是一个规范,是 Google 想要在不同的平台上有一个共通的范畴得以依循。因此,相信未来在 Google 的产品中,于 web、iOS 以及其他装置的介面,都会以 material design 做为标准,而设计出统一的 Google app 的体验。

而在 android 装置上,嘿嘿…毕竟是 Google 定义的嘛~那自然这部份的弹性就多于其他平台喽。所以,私以为就别太过纠结于导览文件上的定义,只要符合目前所看到的三种样式,应该都可以被视 android 装置上的 material design 啦。大家也可以放宽心的运用这三种样式去做 android app 的设计喽。

当然,以上纯粹个人观点,不代表官方立场,除非有 Google 的人在本篇中给予留言,做认证喽 :

最後,附上本篇所有範例的程式連結,還請大家多多指教嘍。