Android Material Design之NavigationView

概要

NavigationView是Google在2015年I/O大会上增加的一个Material Design组件,在本次大会之前Android的抽屉导航可以说是千奇百怪,各种不同风格,这次NavigationView的发布,使得我们可以更简单的更迅速的创建一个统一风格的抽屉导航。而NavigationView的典型用途就是配合之前的DrawerLayout,作为Drawer的一个部分,即导航菜单的本体部分。NavigationView是一个导航菜单框架,使用menu资源填充数据,使我们可以更简单高效的实现导航菜单。它提供了不错的默认样式、选中项高亮、分组单选、分组子标题、以及可选的Header。

先看下面两张图片,两种效果图都是用NavigationView实现的。

效果图一应该有些应用很早之前就已经开始实现了,如网易新闻、MOOC同学、知乎等,返回按钮动画有些应用大概使用了第三方控件如material-menu,但是想要实现如效果图二的效果,这个大概只有最新版的NavigationView可以实现了,不但更改了状态栏的色值,而且抽屉导航划过之处处于半透明状态。当然了就这一点来讲,也不应该说非常的实用,因为该效果仅支持Android4.4以上版本。

NavigationView menu

在使用NavigationView之前先导入相关包,本文所用的是Android support_r23.1.1,当然不是里面所有的包在本文都会用到,导入三个包就可以了:android-support-v7-appcompat、design、recyclerview,至于为什么导入RecyclerView的包,下文中会讲到。既然上面效果图二是NavigationView才可以实现的效果,那么我们就先从图二开始讲解。

先看一下最基本的NavgationView布局文件(图二布局文件):

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:fitsSystemWindows="true"
            android:layout_width="match_parent"
           	android:layout_height="wrap_content"
            android:background="?attr/colorPrimary" />

        <LinearLayout
            android:id="@+id/ll_main"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@color/color1"
            android:orientation="vertical" >

        </LinearLayout>
    </LinearLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigationView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/menu_drawer" />

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

从NavigationView布局中我们可以看到加入了一个app:menu属性,这就是前面所述的NavigationView已经作为了导航菜单的本体部分, 让我们更简单高效得实现风格统一的app,下面就是menu_drawer布局文件:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" >
    <group android:checkableBehavior="single" >
        <item
            android:id="@+id/menu_mycourse"
            android:checked="true"
            android:icon="@drawable/ic_home_grey600_24dp"
            android:title="我的课程"/>
        <item
            android:id="@+id/menu_findcourse"
            android:icon="@drawable/ic_subject_grey600_24dp"
            android:title="发现课程"/>
        <item
            android:id="@+id/menu_setting"
            android:icon="@drawable/main_nav_ic_setting_grey_24dp"
            android:title="应用设置"/>
    </group>

</menu>

该布局文件跟普通的菜单使用时一样的,用菜单来填充导航菜单内容,再合适不过了,在初始化数据是我们可以设置某个菜单项是选中状态,只需要在菜单项上设置android:checked="true",是不是非常方面了,还有更方面的呢,平常我们导航的选中状态如果图标需要更换色彩,有时大概同一种图标需要切两种状态图标,现在就不需要再这么做了,使用NavigationView会自动根据当前主题设置菜单项每一种状态的色值,导航菜单还支持二级子菜单。

<item android:title="应用设置">
    <menu>
        <item
            android:id="@+id/menu_downloading"
            android:checkable="true"
            android:icon="@drawable/ic_file_download_white_24dp"
            android:title="我的下载"/>
        <item
            android:id="@+id/menu_exit"
            android:checkable="true"
            android:icon="@drawable/ic_remove_circle_grey600_24dp"
            android:title="退出账号"/>
    </menu>
</item>

注意在使用子菜单时,需要把android:checkableBehavior="single"属性去除,同时在每一条栏目上添加属性android:checkable="true",否则子菜单不会有点击选中效果

NavigationView Header

NavigationView布局文件中我们还可以看到一个属性app:headerLayout,就是添加一个布局,跟我们平常说的@layout引入另一个布局文件差不多,当然了该属性并不是必须的,本文上面截图所用的布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#3385ff"
    android:gravity="center_horizontal"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    android:padding="10dp" >
    <com.sunny.demo.view.CircleImageView
        android:id="@+id/imageView"
        android:layout_width="180dp"
        android:layout_height="120dp"
        android:layout_marginTop="20dp"
        android:src="@drawable/image" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="UserName"
        android:textColor="#fff"
        android:textSize="24sp" />
</LinearLayout>

既然说到布局的Header部分了,这里就多数一些,在Android support_r23.1.0之前,NavigationView菜单项事实上是继承自ListView,而从support_r23.1.0开始,NavigationView继承自RecyclerView,我把support包升级调试一个功能时,总是报很奇怪的错误,后来在网上找了相关文章,查看了一下源代码才发现,原来新包已经开始使用RecyclerView了,而我在代码中并没有引入RecyclerView相关的lib,当时为什么要调试升级support包,下文中将会说到,因为也是从support_r23.1.0开始,菜单项开始支持了自定义布局,这可是一个相当有用的功能,例如我们侧栏添加一个切换主题的switchbutton或者有一些消息提醒时消息菜单项添加一个红色小标记等等。

既然可以添加Header,那么我们如何动态更改Header布局中相应控件的信息呢,代码也很简洁,如下:

View headerLayout=navigationView.getHeaderView(0);
CircleImageView iv=(CircleImageView) headerLayout.findViewById(R.id.imageView);
iv.setImageResource(R.drawable.image02);

一般情况下只有一个Header,基本上这种处理方式就可以满足我们正常开发的需要了。 当然了我们可以选择不使用Header,下面是效果图:

NavigationView 事件监听

事件监听NavigationView提供了setNavigationItemSelectedListener(NavigationView.OnNavigationItemSelectedListener listener)来监听菜单条目的动作。

navigationView.setNavigationItemSelectedListener(naviListener);
...
private NavigationView.OnNavigationItemSelectedListener naviListener = new NavigationView.OnNavigationItemSelectedListener() {
	@Override
	public boolean onNavigationItemSelected(MenuItem menuItem) {
		// 点击NavigationView中定义的menu item时触发反应
		switch (menuItem.getItemId()) {
		case R.id.menu_mycourse:
			menuItem.setChecked(true);
			ll_main.setBackgroundResource(R.color.color1);
			break;
		
		...
		}
		mDrawerLayout.closeDrawer(navigationView);
		return true;
	}
};

状态栏半透明效果

上面也说了该效果只能在Android4.4及其以上版本才能实现,首先增加一个资源文件res/values-v19/styles.xml,内容如下,就可以达到我们的效果了。

<style name="AppTheme" parent="AppBaseTheme">
	<item name="android:windowTranslucentStatus">true</item>
</style>

添加自定义布局或者属性

在开发过程中Android本身提供的布局可能并不能满足我们开发的需求,那么这时候就需要自定义布局了,使用NavigationView添加自定义布局也相当容易,menu菜单有一个属性是该属性actionLayout,使用该属性就可以引入我们需要的布局文件了,当然了这种引入方式自定义效果有限,不过既然是导航嘛,功能自然不需要太复杂,所以基本可以满足我们需要。

<item
	android:id="@+id/menu_findcourse"
	android:icon="@drawable/ic_subject_grey600_24dp"
	app:actionLayout="@layout/custom_menu"
	android:title="发现课程"/>

custom_menu布局文件如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    
    <TextView 
        android:id="@+id/tv_num"
        android:text="15"
        android:layout_gravity="end"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</LinearLayout>

自定义布局已经定义好,那么我们该如何在代码中动态获取相关控件呢,直接上代码:

Menu menu = navigationView.getMenu();
MenuItem menuItem = menu.findItem(R.id.menu_findcourse);
View actionView =MenuItemCompat.getActionView(menuItem);
TextView tv_num=(TextView) actionView.findViewById(R.id.tv_num);
tv_num.setText("50");

最终效果如下:

如果我们想进一步实现子定义图标及文字色彩呢,系统也为我们提供了相应的属性

  • app:itemBackground 设置背景色
  • app:itemIconTint 设置图标色
  • app:itemTextAppearance 设置文本外观
  • app:itemTextColor 设置文本色值

小结

NavigationView常用的一些功能基本介绍完了,当然了如果还不能满足开发者需求,那么可以使用第三方库或者自己实现。

参考了网上的文章,下面这张截图就是完全自定义实现抽屉导航的Item,当然了还没有完全实现效果,有兴趣可以参考源代码再继续深入下去:

文章开始第一幅效果图的布局跟这个布局基本上是一致的,只是将抽屉导航NavigationView换成了ListView,可以下载源代码查看。

示例源代码下载

参考资料

Fragment Navigation Drawer

Android Design Support Library初探,NavigationView实践

Android 自己实现 NavigationView [Design Support Library(1)]

评论

您确定要删除吗?删除之后不可恢复