Android 自己實現 NavigationView [Design Support Library(1)]
轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/46405409;
本文出自:【張鴻洋的博客】
一、概述
Google I/O 2015 給大家帶來了Android Design Support Library,對于希望做md風格的app的來說,簡直是天大的喜訊了~大家可以通過Android Design Support Library該文章對其進行了解,也可以直接在github上下載示例代碼運行學習。為了表達我心中的喜悅,我決定針對該庫寫一系列的文章來分別介紹新增加的控件。
ok,那么首先介紹的就是NavigationView。
注意下更新下as的SDK,然后在使用的過程中,在build.gradle中添加:
compile 'com.android.support:design:22.2.0'
在md風格的app中,例如如下風格的側滑菜單非常常見:

在之前的設計中,你可能需要考慮如何去布局實現,例如使用ListView;再者還要去設計Item的選中狀態之類~~
but,現在,google提供了NavigationView,你只需要寫寫布局文件,這樣的效果就ok了,并且兼容到Android 2.1,非常值得去體驗一下。接下來我們來介紹如何去使用這個NavigationView!
二、使用
使用起來very simple ,主要就是寫寫布局文件~
(一)布局文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout android:id="@+id/id_drawer_layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" >
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v7.widget.Toolbar android:id="@+id/id_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<TextView android:id="@+id/id_tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="HelloWorld" android:textSize="30sp"/>
</RelativeLayout>
<android.support.design.widget.NavigationView android:id="@+id/id_nv_menu" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="left" android:fitsSystemWindows="true" app:headerLayout="@layout/header_just_username" app:menu="@menu/menu_drawer" />
</android.support.v4.widget.DrawerLayout>
可以看到我們的最外層是DrawerLayout,里面一個content,一個作為drawer。我們的drawer為NavigationView。
注意這個view的兩個屬性app:headerLayout="@layout/header_just_username"和app:menu="@menu/menu_drawer",分別代表drawer布局中的header和menuitem區域,當然你可以根據自己的情況使用。
接下來看看header的布局文件和menu配置文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="192dp" android:background="?attr/colorPrimaryDark" android:orientation="vertical" android:padding="16dp" android:theme="@style/ThemeOverlay.AppCompat.Dark">
<TextView android:id="@+id/id_link" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="16dp" android:text="http://blog.csdn.net/lmj623565791"/>
<TextView android:id="@+id/id_username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/id_link" android:text="Zhang Hongyang"/>
<ImageView android:layout_width="72dp" android:layout_height="72dp" android:layout_above="@id/id_username" android:layout_marginBottom="16dp" android:src="@mipmap/icon"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/nav_home" android:icon="@drawable/ic_dashboard" android:title="Home"/>
<item android:id="@+id/nav_messages" android:icon="@drawable/ic_event" android:title="Messages"/>
<item android:id="@+id/nav_friends" android:icon="@drawable/ic_headset" android:title="Friends"/>
<item android:id="@+id/nav_discussion" android:icon="@drawable/ic_forum" android:title="Discussion"/>
</group>
<item android:title="Sub items">
<menu>
<item android:icon="@drawable/ic_dashboard" android:title="Sub item 1"/>
<item android:icon="@drawable/ic_forum" android:title="Sub item 2"/>
</menu>
</item>
</menu>
別放錯文件夾哈~
布局文件寫完了,基本就好了,是不是很爽~看似復雜的效果,寫寫布局文件就ok。
ps:默認的顏色很多是從當前的主題中提取的,比如icon的stateColor,當然你也可以通過以下屬性修改部分樣式:
app:itemIconTint="" app:itemBackground="" app:itemTextColor=""
(二)Activity
最后是Activity:
package com.imooc.testandroid;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
public class NavigationViewActivity extends ActionBarActivity {
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation_view);
mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
mNavigationView = (NavigationView) findViewById(R.id.id_nv_menu);
Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
setSupportActionBar(toolbar);
final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);
setupDrawerContent(mNavigationView);
}
private void setupDrawerContent(NavigationView navigationView)
{
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener()
{
@Override
public boolean onNavigationItemSelected(MenuItem menuItem)
{
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
return true;
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_navigation_view, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if(item.getItemId() == android.R.id.home)
{
mDrawerLayout.openDrawer(GravityCompat.START);
return true ;
}
return super.onOptionsItemSelected(item);
}
}
我們在Activity里面可以通過navigationView去navigationView.setNavigationItemSelectedListener,當selected的時候,menuItem去setChecked(true)。
別忘了設置theme~
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> </style>
<style name="Theme.DesignDemo" parent="Base.Theme.DesignDemo"> </style>
<style name="Base.Theme.DesignDemo" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">#673AB7</item> <item name="colorPrimaryDark">#512DA8</item> <item name="colorAccent">#FF4081</item> <item name="android:windowBackground">@color/window_background</item> </style>
</resources>
<color name="window_background">#FFF5F5F5</color>
<activity android:name=".NavigationViewActivity" android:label="@string/title_activity_navigation_view" android:theme="@style/Theme.DesignDemo">
</activity>
ok,到此就搞定了~~
不過存在一個問題,此時你如果點擊Sub items里面的Sub item,如果你期望當前選中應該是Sub item,你會發現不起作用。那怎么辦呢?
(三)Sub Item支持Cheable
這里可以修改menu的配置文件:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<item android:id="@+id/nav_home" android:checkable="true" android:icon="@drawable/ic_dashboard" android:title="Home"/>
<item android:id="@+id/nav_messages" android:checkable="true" android:icon="@drawable/ic_event" android:title="Messages"/>
<item android:id="@+id/nav_friends" android:checkable="true" android:icon="@drawable/ic_headset" android:title="Friends"/>
<item android:id="@+id/nav_discussion" android:checkable="true" android:icon="@drawable/ic_forum" android:title="Discussion"/>
</group>
<item android:title="Sub items">
<menu>
<item android:checkable="true" android:icon="@drawable/ic_dashboard" android:title="Sub item 1"/>
<item android:checkable="true" android:icon="@drawable/ic_forum" android:title="Sub item 2"/>
</menu>
</item>
</menu>
將android:checkableBehavior="single"去掉,然后給每個item項添加android:checkable="true"。
然后在代碼中:
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener()
{
private MenuItem mPreMenuItem;
@Override
public boolean onNavigationItemSelected(MenuItem menuItem)
{
if (mPreMenuItem != null) mPreMenuItem.setChecked(false);
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
mPreMenuItem = menuItem;
return true;
}
});
存一下preMenuItem,手動切換。
效果:

ok,哈~其實這個還是參考鏈接2里面的一個評論說的~~
到此用法就介紹完了有木有一點小激動~ 但是還有個問題,對于app,就像ActionBar最初的出現,一開始大家都歡欣鼓舞,后來發現app中多數情況下需要去定制,尼瑪,是不是忽然覺得ActionBar太死板了,惡心死了(當然了現在有了ToolBar靈活度上好多了)對于上述NavigationView可能也會存在定制上的問題,比如我希望選中的Item左邊有個高亮的豎線之類的效果。那么,針對于各種需求,想要能解決各種問題,最好的方式就是說對于NavigationView的效果自己可以實現。最好,我們就來看看NavigationView自己實現有多難?
三、自己實現NavigationView效果
其實NavigationView的實現非常簡單,一個ListView就可以了,甚至都不需要去自定義,簡單寫一個Adapter就行了~~

首先觀察該圖,有沒有發現神奇之處,恩,你肯定發現不了,因為我們做的太像了。
其實這個圖就是我通過ListView寫的一個~是不是和原版很像(~哈~參考了源碼的實現,當然像。)
接下來分析,如果說是ListView,那么Item的type肯定不止一種,那我們數一數種類:
- 帶圖標和文本的
- 僅僅是文本的 Sub Items
- 分割線
你會說還有頂部那個,頂部是headview呀~~
這么分析完成,是不是瞬間覺得沒有難度了~
(一)首先布局文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout android:id="@+id/id_drawer_layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" >
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v7.widget.Toolbar android:id="@+id/id_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<TextView android:id="@+id/id_tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="HelloWorld" android:textSize="30sp"/>
</RelativeLayout>
<ListView android:id="@+id/id_lv_left_menu" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" android:paddingTop="0dp" android:background="#ffffffff" android:clipToPadding="false" android:divider="@null" android:listSelector="?attr/selectableItemBackground" />
</android.support.v4.widget.DrawerLayout>
布局文件上:和上文對比,我們僅僅把NavigationView換成了ListView.
下面注意了,一大波代碼來襲.
(二) Activity
package com.imooc.testandroid;
public class NavListViewActivity extends ActionBarActivity {
private ListView mLvLeftMenu;
private DrawerLayout mDrawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nav_list_view);
mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
mLvLeftMenu = (ListView) findViewById(R.id.id_lv_left_menu);
Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
setSupportActionBar(toolbar);
final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);
setUpDrawer();
}
private void setUpDrawer()
{
LayoutInflater inflater = LayoutInflater.from(this);
mLvLeftMenu.addHeaderView(inflater.inflate(R.layout.header_just_username, mLvLeftMenu, false));
mLvLeftMenu.setAdapter(new MenuItemAdapter(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_nav_list_view, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == android.R.id.home)
{
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
}
直接看onCreate中的setUpDrawer(),可以看到我們首先去addHeadView,然后去setAdapter。
那么核心代碼就是我們的Adapter了~~
(三)MenuItemAdapter
public class LvMenuItem {
public LvMenuItem(int icon, String name)
{
this.icon = icon;
this.name = name;
if (icon == NO_ICON && TextUtils.isEmpty(name))
{
type = TYPE_SEPARATOR;
} else if (icon == NO_ICON)
{
type = TYPE_NO_ICON;
} else
{
type = TYPE_NORMAL;
}
if (type != TYPE_SEPARATOR && TextUtils.isEmpty(name))
{
throw new IllegalArgumentException("you need set a name for a non-SEPARATOR item");
}
L.e(type + "");
}
public LvMenuItem(String name)
{
this(NO_ICON, name);
}
public LvMenuItem()
{
this(null);
}
private static final int NO_ICON = 0;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_NO_ICON = 1;
public static final int TYPE_SEPARATOR = 2;
int type;
String name;
int icon;
}
public class MenuItemAdapter extends BaseAdapter {
private final int mIconSize;
private LayoutInflater mInflater;
private Context mContext;
public MenuItemAdapter(Context context)
{
mInflater = LayoutInflater.from(context);
mContext = context;
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.drawer_icon_size);//24dp
}
private List<LvMenuItem> mItems = new ArrayList<LvMenuItem>(
Arrays.asList(
new LvMenuItem(R.drawable.ic_dashboard, "Home"),
new LvMenuItem(R.drawable.ic_event, "Messages"),
new LvMenuItem(R.drawable.ic_headset, "Friends"),
new LvMenuItem(R.drawable.ic_forum, "Discussion"),
new LvMenuItem(),
new LvMenuItem("Sub Items"),
new LvMenuItem(R.drawable.ic_dashboard, "Sub Item 1"),
new LvMenuItem(R.drawable.ic_forum, "Sub Item 2")
));
@Override
public int getCount()
{
return mItems.size();
}
@Override
public Object getItem(int position)
{
return mItems.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public int getViewTypeCount()
{
return 3;
}
@Override
public int getItemViewType(int position)
{
return mItems.get(position).type;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
LvMenuItem item = mItems.get(position);
switch (item.type)
{
case LvMenuItem.TYPE_NORMAL:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item, parent,
false);
}
TextView itemView = (TextView) convertView;
itemView.setText(item.name);
Drawable icon = mContext.getResources().getDrawable(item.icon);
setIconColor(icon);
if (icon != null)
{
icon.setBounds(0, 0, mIconSize, mIconSize);
TextViewCompat.setCompoundDrawablesRelative(itemView, icon, null, null, null);
}
break;
case LvMenuItem.TYPE_NO_ICON:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item_subheader,
parent, false);
}
TextView subHeader = (TextView) convertView;
subHeader.setText(item.name);
break;
case LvMenuItem.TYPE_SEPARATOR:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item_separator,
parent, false);
}
break;
}
return convertView;
}
public void setIconColor(Drawable icon)
{
int textColorSecondary = android.R.attr.textColorSecondary;
TypedValue value = new TypedValue();
if (!mContext.getTheme().resolveAttribute(textColorSecondary, value, true))
{
return;
}
int baseColor = mContext.getResources().getColor(value.resourceId);
icon.setColorFilter(baseColor, PorterDuff.Mode.MULTIPLY);
}
}
首先我們的每個Item對應于一個LvMenuItem,包含icon、name、type,可以看到我們的type分為3類。
那么adapter中代碼就很簡單了,多Item布局的寫法。
這樣就完成了,我們自己去書寫NavigationView,是不是很簡單~~如果你的app需要類似效果,但是又與NavigationView的效果并非一模一樣,可以考慮按照上面的思路自己寫一個。
最后貼一下用到的Item的布局文件:
- design_drawer_item_subheader.xml
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?attr/listPreferredItemHeightSmall" android:gravity="center_vertical|start" android:maxLines="1" android:paddingLeft="?attr/listPreferredItemPaddingLeft" android:paddingRight="?attr/listPreferredItemPaddingRight" android:textAppearance="?attr/textAppearanceListItem" android:textColor="?android:textColorSecondary"/>
- design_drawer_item_separator.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content">
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="?android:attr/listDivider"/>
</FrameLayout>
- design_drawer_item,xml:
<?xml version="1.0" encoding="utf-8"?> <TextView 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:drawablePadding="32dp" android:gravity="center_vertical|start" android:maxLines="1" android:textAppearance="?attr/textAppearanceListItem" android:textColor="?android:attr/textColorPrimary"/>
ok,其實上述ListView的寫法也正是NavigationView的源碼實現~~item的布局文件直接從源碼中拖出來的,還是爽爽噠~
源碼點擊下載
~~hava a nice day ~~
微信公眾號:hongyangAndroid
(歡迎關注,第一時間推送博文信息)
參考鏈接
來自: http://blog.csdn.net//lmj623565791/article/details/46405409
