左侧导航抽屉框的使用
在常见的导航范例中(作者开发的集锦),我们讨论了在android app 中可以使用的导航结构,而在其中最灵活的要数Navigation Drawer。 在谷歌的2015年I/O 大会上,谷歌发布了之前创建Navigation Drawer的文档指导更加简单的Navigation View.
随着Android 5.0 Lolipop版本的发布,新式导航抽屉的设计风格的高度跨越了整个屏幕的高度,超过了ActionBar并且重叠了透明的StatusBar。当你自己在设计自己的导航抽屉风格时,可以参考导航抽屉的主要涉及风格文档 。
使用
可以定义多种fragments 和抽屉项目列表中的多个选项,点击每一个项目都可以在activity的内容视图上显示出相应的fragment
建立
在使用谷歌新发布的NavigationView 之前,确保你已经安装了Android M版本发布的谷歌设计支持库。NavigationView已经可以兼容Android 2.1之前的所有版本。
下载导航抽屉选项图标
下载下面的图标并且将它们复制粘贴到drawable文件夹中,或者用 New Image Asset 对话框为每一个密度都创建版本。
建立 抽屉资源
创建一个 menu/drawer_view.xml 文件(注意是menu 文件夹里):
<menuxmlns:android="http://schemas.android.com/apk/res/android">
<groupandroid:checkableBehavior="single">
<item android:id="@+id/nav_first_fragment"
android:icon="@drawable/ic_one"
android:title="First"/>
<item android:id="@+id/nav_second_fragment"
android:icon="@drawable/ic_two"android:title="Second"/>
<item android:id="@+id/nav_third_fragment"
android:icon="@drawable/ic_three"
android:title="Third"/></group>
</menu>
你可以通过设置
android:checked="true" 属性,在上面的item中选择一个作为默认勾选项
同时你也可以在上面文件中继续创建副标题和组元素,具体如下:
<itemandroid:title="Sub items"><menu><itemandroid:icon="@drawable/ic_dashboard"android:title="Sub item 1"/><itemandroid:icon="@drawable/ic_forum"android:title="Sub item 2"/></menu></item>
导航视图也有接受标题布局引用的典型属性,举例来说,你可以建立一个layout/nav_header.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="192dp" android:background="?attr/colorPrimaryDark" android:padding="16dp" android:theme="@style/ThemeOverlay.AppCompat.Dark" android:orientation="vertical" android:gravity="bottom"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Header" android:textColor="@android:color/white" android:textAppearance="@style/TextAppearance.AppCompat.Body1"/> </LinearLayout>
接下来,你可以定义展示在抽屉内部的fragments,这些fragments 可以是你定义在应用中的任意支持的fragment,同时它们必须继承 android.support.v4.app.Fragment
建立 ToolBar
为了可以让你的导航抽屉可以跨越ActionBar, 我们需要在AppCompat V21 library 定义使用新的Toolbar物件。Toolbar能够嵌进视图层里从而可以让抽屉划过Actionbar
用下面的代码创建一个新的布局文件 res/layout/toobar.xml
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:minHeight="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:background="?attr/colorPrimaryDark">
</android.support.v7.widget.Toolbar
?attr 的使用 :
?attr表示引用的是当前主题中的资源。
注意到 fitsSystemWindow 属性,当设置成true的时候视图会被布置跟 StatusBar 和 ActionBar 存在一样,举例来说,最顶层的UI界面就会有一定的宽度使导航条不被遮挡。如果没有这个属性,ToolBar 就不会留空白。
一般来讲我们是希望在内容视图上方留有导航条的,因此我们会将ToolBar 的 fitsSystemWindows属性设置为true.
既然使用了ToolBar 作为ActionBar ,那么就需要将默认的ActionBar屏蔽掉,<应该是有很多方法的> ,这里通过在 styles.xml 文件里设置应用主题来实现
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#673AB7</item>
<item name="colorPrimaryDark">#512DA8</item>
<item name="colorAccent">#FF4081</item>
</style>
</resources>
在这里你可以决定你自己的颜色主题通过 Material Pallete 网站(非常好的颜色搭配网站)来选择你的Primary 和Dark Primary 颜色。在本文例子中,作者选择了紫色基调的颜色。
注意: 如果你忘记了在styles.xml 将actionbar 去掉,你就会得到这样的异常信息,
This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead. 如果你看到这条信息,请参照上面的步骤。
在Activity 中建立抽屉
接下来,我们就要使用 res/layout/activity_main.xml 的布局文件来建立基本的导航抽屉了。注意ToolBar 是通过include标签添加到主要的内容视图中的第一个孩子视图。
<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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- The ActionBar -->
<include
layout="@layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- The main content view -->
<FrameLayout
android:id="@+id/flContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<!-- The navigation drawer -->
<android.support.design.widget.NavigationView
android:id="@+id/nvView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/white"
app:menu="@menu/drawer_view"app:headerLayout="@layout/nav_header"
/>
</android.support.v4.widget.DrawerLayout>
现在,让我们在自己的Activity上建立抽屉,同时我们也可以建立目录的图标
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawer;
private Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set a Toolbar to replace the ActionBar.
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Find our drawer view
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
// Set the menu icon instead of the launcher icon.
final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
// Uncomment to inflate menu items to Action Bar
// inflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
switch (item.getItemId()) {
case android.R.id.home:
mDrawer.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
}
}
使用一个handler来对导航元素上的点击事件进行反应并且置换fragment
@Override
protected void onCreate(Bundle savedInstanceState) {
// Find our drawer view
nvDrawer = (NavigationView) findViewById(R.id.nvView);
// Setup drawer view
setupDrawerContent(nvDrawer);
}
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
selectDrawerItem(menuItem);
return true;
}
});
}
public void selectDrawerItem(MenuItem menuItem) {
// Create a new fragment and specify the planet to show based on
// position
Fragment fragment = null;
Class fragmentClass;
switch(menuItem.getItemId()) {
case R.id.nav_first_fragment:
fragmentClass = FirstFragment.class;
break;
case R.id.nav_second_fragment:
fragmentClass = SecondFragment.class;
break;
case R.id.nav_third_fragment:
fragmentClass = ThirdFragment.class;
break;
default:
fragmentClass = FirstFragment.class;
}
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit();
// Highlight the selected item, update the title, and close the drawer
menuItem.setChecked(true);
setTitle(menuItem.getTitle());
dlDrawer.closeDrawers(); // 这里写错了么? nvDrawer?closeDrawers() 是不是要另外写?how?
}
汉堡包图标的动画效果
为了让汉堡包图标(三条横线的图标)拥有动画效果从而表明抽屉是打开的还是关闭的,我们需要使用类 ActionBarDrawrToggle
在 res/values/strings.xml 中添加:
<resources>
<string name="drawer_open">Open navigation drawer</string>
<string name="drawer_close">Close navigation drawer</string>
</resources>
我们需要将图标的布局和Toolbar联系起来
protected void onCreate(Bundle savedInstanceState) {
// Set a Toolbar to replace the ActionBar.
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Find our drawer view
dlDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); // 这里又变成这个名字了
drawerToggle = setupDrawerToggle();
// Tie DrawerLayout events to the ActionBarToggle
dlDrawer.setDrawerListener(drawerToggle); // 所谓的联系起来
}
private ActionBarDrawerToggle setupDrawerToggle() {
return new ActionBarDrawerToggle(this, dlDrawer, toolbar, R.string.drawer_open, R.string.drawer_close);
}
接下里,我们需要在屏幕恢复或者是设置改变时保持状态的同步
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggles
drawerToggle.onConfigurationChanged(newConfig);
}
<仅仅是继承了两个回调函数>
我们需要改变
onOptionsItemSelected()方法 并且允许 ActionBarToggle 来处理这个问题
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
ActionBarToggle 会执行跟之前相同的功能,但添加了更多的检查并允许鼠标点击图标来打开和关闭抽屉。
你不再需要 ic_menu.png 和调用 setHomeAsUpIndicator()来设置目录。 ActionBarDrawerToggle 为你渲染了一个典型DrawerArrowDrawable
让Status Bar 变透明
为了让status Bar 变透明并且让抽屉划过,我们需要设置属性 android:windowTranslucentStatus为true,因为这种方式对 Kitkat之前的设备不支持,我们需要添加res/values-v19/styles.xml 文件为 API 19 和之前的版本。
注意: 如果你只更改 res/values/styles.xml 中的 android:windowTranslucentStatus属性,那么你必须只能构建在版本19或者更改的SDK上,很显然这会限制你支持更老的设备
在res/values-19/styles.xml 中:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>
限制你可以将你的app跑起来,你可以看到导航抽屉并且能够在你的fragment中进行选择。
限制
当前版本的设计支持库存在一些限制, 主要问题是系统会在导航目录上高亮你当前的条目, NavigationView的itemBackground属性不能够正确的处理所选的状态,无论是高亮的条目或者不选择的条目,这让这个属性基本对大部分应用失效, 当在导航条目中选择 副标题时,同样高亮不会像预期那样工作,更新副标题中被选择的条目会让高亮 覆盖部分一起消失。 最后处理选择条目的高亮效果是一件麻烦的事情,而这件事情需要应用自己来解决
Fragment 的替代品
尽管很多导航抽屉在内容的展示上选择了fragment, 你也可以使用 RelativieLayout/LinearLayout 如果你希望使用抽屉来覆盖当前所展示的Activity 。
你可以用linearlayout 来代替FrameLayout
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/drawer_layout">
<LinearLayout
android:id="@+id/content_frame"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- The navigation drawer -->
<ListView android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
</android.support.v4.widget.DrawerLayout>
同样:
getFragmentManager().beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
你可以用Layout 的容纳盒直接渲染Activity
LayoutInflater inflater = getLayoutInflater();
LinearLayout container = (LinearLayout) findViewById(R.id.content_frame);
inflater.inflate(R.layout.activity_main, container);
参考