一、DrawerLayout 抽屉布局
二、案例
附 代码补充
三、案例二:抽屉布局结合SwipeTab
一、DrawerLayout 抽屉布局
1. DrawerLayout的使用场景
(1)超过三个顶级视图(否则可用 Tab 导航)
(2)需要从底层视图切换导航位置
(3)分支视图层次较深
2. 实现抽屉导航
相关类:
(1)抽屉布局:android.support.v4.widget.DrawerLayout
(2)抽屉把手:android.support.v4.app.ActionBarDrawerToggle
需要在工程中先导入 support-v4 。
二、案例
1. 抽屉布局需要用到v4支持包,因此先导入v4支持包:在 build.gradle 中加入以下导包语句:
compile 'com.android.support:support-v4:21.0.3'
2. 在 strings.xml 中定义所需字符串:
<resources> <string name="app_name">DrawerNav</string> <string name="action_settings">Settings</string> <string name="drawer_open">open</string> <string name="drawer_close">close</string> <string name="search">搜索</string> <string name="title">目录</string> </resources>
3. 复制 icon 到 res 下面:
4. 在 menu_main.xml 中定义 search 菜单 (搜索菜单):
<item android:id="@+id/action_search" android:title="@string/search" android:orderInCategory="101" android:showAsAction="always" android:icon="@drawable/ic_search_white_24dp"/>
在 menu_main.xml 中,原本就有设置菜单(settings),加入搜索菜单后,界面如下:
menu_main.xml 代码如下:
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <item android:id="@+id/action_settings" android:title="@string/action_settings" android:orderInCategory="100" android:showAsAction="never"/> <item android:id="@+id/action_search" android:title="@string/search" android:orderInCategory="101" android:showAsAction="always" android:icon="@drawable/ic_search_white_24dp"/> </menu>
5. 在 activity_main.xml 中定义内容视图(帧布局)和左侧、右侧两个抽屉:
<android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> <!--内容视图--> <FrameLayout android:id="@+id/content_layout" android:layout_width="match_parent" android:layout_height="match_parent"/> <!-- 左侧抽屉 : 因为左侧抽屉,写了一个单独的布局文件 drawer_left,因此要包含进来。 单独写布局文件,内容可以更丰富一些。 --> <!-- layout_gravity:定义抽屉的位置(start:左侧;end:右侧。) --> <include layout="@layout/drawer_left" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start"/> <!-- 右侧抽屉 : 因为右侧抽屉没有单独写布局文件,因此只需写一个控件 ListView 即可。 --> <ListView android:id="@+id/right_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="end" android:background="@android:color/white"/> </android.support.v4.widget.DrawerLayout>
其中,左侧抽屉写了一个单独的布局文件 drawer_left.xml,其界面如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="150dp" android:background="@android:color/darker_gray"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@mipmap/ic_launcher"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/app_name" android:layout_gravity="center" android:paddingTop="30dp"/> </LinearLayout> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp"/> </LinearLayout> <!-- 上面的 LinearLayout 不会被调用,因此不需要写 id。 下面的 ListView 会在里面添加内容,因此需要写 id。 -->
6. MainActivity 。 声明变量。
// 声明变量 private String[] data = {"选项一", "选项二", "选项三", "选项四", "选项五"}; private DrawerLayout drawerLayout; private ActionBarDrawerToggle toggle; private ListView listView;
7. MainActivity 。 初始化变量(同时设置抽屉的阴影、设置显示抽屉把手)。
// 初始化变量 drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // 设置抽屉的阴影 drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.END); // 显示 Home 按钮(此处显示抽屉把手) getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true);
8. MainActivity 。 创建抽屉把手(可以部署到手机上看效果)。
// 创建抽屉把手(看效果) // 参数:上下文,抽屉布局对象,把手菜单图标,打开抽屉的文本,关闭抽屉的文本 toggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_menu_white_24dp, R.string.drawer_open, R.string.drawer_close) { // 抽屉打开 @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); getActionBar().setTitle(getString(R.string.title)); } // 抽屉关闭 @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); } }; // 同步状态(更新 Home 位置显示的图标) toggle.syncState(); // 设置监听器 drawerLayout.setDrawerListener(toggle);
9. MainActivity 。 写菜单事件(搜索图标)。完成后可以在手机上部署看效果。
// 菜单 (看效果:要重绘菜单) @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_main, menu); // 8.1 控制搜索图标的显示/隐藏 // 左侧抽屉打开,隐藏操作栏按钮 // 左侧抽屉关闭,显示操作栏按钮 boolean isOpen = drawerLayout.isDrawerOpen(Gravity.START); menu.findItem(R.id.action_search).setVisible(!isOpen); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // 8.2 点击抽屉把手,打开/关闭抽屉 if (toggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); }
接着在上一步的“创建抽屉把手”中的打开抽屉(onDrawerOpened)和关闭抽屉(onDrawerClosed)的类中,重新绘制菜单(不重新绘制的时候,菜单没有改变)
invalidateOptionsMenu();
10. MainActivity 。 初始化左侧抽屉。
(1)在 onCreate()方法中,调用自己创建的初始化左侧抽屉的方法:initLeftDrawer() 。
// 初始化左侧抽屉 initLeftDrawer();
(2)在initLeftDrawer()方法中,将 左侧抽屉的listView(列表框)、适配器、数据 进行绑定 。
// 左侧抽屉的listView(列表框)、适配器、数据 进行绑定 // 之所以能找到 listView,是因为 activity_main.xml 把它 include 了。 listView = (ListView) findViewById(R.id.listView); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_activated_1, data); // 设置单选模式 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setAdapter(adapter);
补充:
simple_list_item_1 :有一个控件。
simple_list_item_2 :有两个控件。
simple_list_item_activated_1 : 有一个控件。当选项选中时,背景色改变(即为激活状态)
simple_list_item_activated_2 : 有两个控件。当选项选中时,背景色改变(即为激活状态)
(3)在initLeftDrawer()方法中,写 点击左侧抽屉ListView(列表框)的事件。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 点击ListView中的项的时候,就设置标题 getActionBar().setTitle(data[position]); // 点击完项后,关闭左侧抽屉 drawerLayout.closeDrawer(Gravity.START);
11. MainActivity 。 创建两个碎片:NewsFragment & fragment_news.xml,TechFragment & fragment_tech.xml。
12.当选中左侧抽屉中ListView中的不同选项的时候,使用不同的 碎片 替换 内容视图。
(1)在“声明变量”部分,定义 碎片管理器
// 定义 碎片管理器 private FragmentManager fm;
(2)在“初始化变量”部分,获得 碎片管理器
// 获得 碎片管理器 fm = this.getFragmentManager();
(3)在“初始化左侧抽屉”的点击ListView事件部分,当选中“选项一”的时候,用 News 碎片 替换 内容视图;当选中“选项二”的时候,用 Tech 碎片 替换 内容视图。
FragmentTransaction ft = fm.beginTransaction(); switch (position) { case 0: ft.replace(R.id.content_layout, new NewsFragment()); break; case 1: ft.replace(R.id.content_layout, new TechFragment()); break; default: break; } // 提交事务 ft.commit();
附 代码补充
MainActivity
package com.xiangdong.drawernav; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { // 5. 声明变量 private String[] data = {"选项一", "选项二", "选项三", "选项四", "选项五"}; private DrawerLayout drawerLayout; private ActionBarDrawerToggle toggle; private ListView listView; // 11.1 定义 碎片管理器 private FragmentManager fm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 6. 初始化变量 drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // 设置抽屉的阴影 drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.END); // 显示 Home 按钮(此处显示抽屉把手) getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); // 11.2 获得 碎片管理器 fm = this.getFragmentManager(); // 7. 创建抽屉把手(看效果) // 参数:上下文,抽屉布局对象,把手菜单图标,打开抽屉的文本,关闭抽屉的文本 toggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_menu_white_24dp, R.string.drawer_open, R.string.drawer_close) { // 抽屉打开 @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); // 8.3重新绘制菜单 (不重新绘制的时候,菜单没有改变) invalidateOptionsMenu(); getActionBar().setTitle(getString(R.string.title)); } // 抽屉关闭 @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); // 8.3 重新绘制菜单 invalidateOptionsMenu(); } }; // 同步状态(更新 Home 位置显示的图标) toggle.syncState(); // 设置监听器 drawerLayout.setDrawerListener(toggle); // 9. 初始化左侧抽屉 initLeftDrawer(); } /** * 9. 初始化左侧抽屉 */ private void initLeftDrawer() { // 9.1 左侧抽屉的listView(列表框)、适配器、数据 进行绑定 // 之所以能找到 listView,是因为 activity_main.xml 把它 include 了。 listView = (ListView) findViewById(R.id.listView); /* simple_list_item_1 :有一个控件。 simple_list_item_2 :有两个控件。 simple_list_item_activated_1 : 有一个控件。当选项选中时,背景色改变(即为激活状态) simple_list_item_activated_2 : 有两个控件。当选项选中时,背景色改变(即为激活状态) */ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_activated_1, data); // 设置单选模式 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setAdapter(adapter); // 9.2 点击左侧抽屉ListView(列表框)的事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 点击ListView中的项的时候,就设置标题 getActionBar().setTitle(data[position]); // 点击完项后,关闭左侧抽屉 drawerLayout.closeDrawer(Gravity.START); // 10. 创建碎片(XxxFragment & fragment_Xxx.xml) // 11. 使用 碎片 替换 内容视图 // 11.3 FragmentTransaction ft = fm.beginTransaction(); switch (position) { case 0: ft.replace(R.id.content_layout, new NewsFragment()); break; case 1: ft.replace(R.id.content_layout, new TechFragment()); break; default: break; } // 提交事务 ft.commit(); } }); } // 菜单 (看效果:要重绘菜单) @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_main, menu); // 8.1 控制搜索图标的显示/隐藏 // 左侧抽屉打开,隐藏操作栏按钮 // 左侧抽屉关闭,显示操作栏按钮 boolean isOpen = drawerLayout.isDrawerOpen(Gravity.START); menu.findItem(R.id.action_search).setVisible(!isOpen); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // 8.2 点击抽屉把手,打开/关闭抽屉 if (toggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } }
三、案例二:抽屉布局结合SwipeTab
本案例与上一个案例,唯一不同的是,当点击左侧抽屉中的“选项一”后,加载了一个碎片:MainFragment ,该碎片使用了 SwipeTab。
1. strings.xml
<resources> <string name="app_name">DrawerNav</string> <string name="drawer_open">open</string> <string name="drawer_close">close</string> <string name="search">搜索</string> <string name="title">目录</string> <string name="news">新闻</string> <string name="tech">科技</string> <string name="game">游戏</string> <string name="action_settings">Settings</string> </resources>
2. menu_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <item android:id="@+id/action_settings" android:orderInCategory="100" android:showAsAction="never" android:title="@string/action_settings"/> <item android:id="@+id/action_search" android:icon="@drawable/ic_search_white_24dp" android:orderInCategory="101" android:showAsAction="always" android:title="@string/search"/> </menu>
3. activity_main.xml
<android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 内容视图 --> <FrameLayout android:id="@+id/content_layout" android:layout_width="match_parent" android:layout_height="match_parent"/> <!-- 左侧抽屉 --> <!--layout_gravity: 定义抽屉的位置(start 左侧,end 右侧)--> <include layout="@layout/drawer_left" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start"/> <!--右侧抽屉--> <ListView android:id="@+id/right_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="end" android:background="@android:color/white"/> </android.support.v4.widget.DrawerLayout>
4. 三个碎片的布局:
fragment_news.xml:
<FrameLayout 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="com.android.drawernav.NewsFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/news"/> </FrameLayout>
fragment_tech.xml:
<FrameLayout 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="com.android.drawernav.TechFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/tech"/> </FrameLayout>
fragment_game.xml:
<FrameLayout 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="com.android.drawernav.GameFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/game"/> </FrameLayout>
5. SwipeTab的布局文件和事件:
(1)fragment_main.xml
<android.support.v4.view.ViewPager android:id="@+id/pager" 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"/> <!-- 因为要调用,因此要写 id。 -->
(2)MainFragment
package com.android.drawernav; import android.app.ActionBar; import android.app.FragmentTransaction; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * A simple {@link Fragment} subclass. */ public class MainFragment extends Fragment { private ActionBar actionBar; private String[] tabs = {"新闻", "科技"}; private ViewPager viewPager; private TabFragmentAdapter adapter; public MainFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_main, container, false); actionBar = getActivity().getActionBar(); // 8.创建 ViewPager 适配器 adapter = new TabFragmentAdapter(getActivity().getSupportFragmentManager()); // 获得分页控件 (fragment_main.xml) viewPager = (ViewPager) view.findViewById(R.id.pager); viewPager.setAdapter(adapter); // 9. 设置 Pager 改变的监听器 viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int i, float v, int i1) { } @Override public void onPageSelected(int i) { // Pager 改变时后,选中对应的 Tab actionBar.setSelectedNavigationItem(i); } @Override public void onPageScrollStateChanged(int i) { } }); // 11.设置应用栏导航模式,文本/监听事件 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); if (actionBar.getTabCount() == 0) { for (int i = 0; i < tabs.length; i++) { actionBar.addTab(actionBar.newTab().setText(tabs[i]). setTabListener(tabListener)); } }else{ // 获得 已初始化的 actionBar } return view; } private class TabFragmentAdapter extends FragmentPagerAdapter { public TabFragmentAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int i) { switch (i) { case 0: // 需要兼容低版本:android.support.v4.app.Fragment return new NewsFragment(); case 1: return new TechFragment(); default: return null; } } @Override public int getCount() { return tabs.length; } } private ActionBar.TabListener tabListener = new ActionBar.TabListener() { @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // 点击 Tab,ViewPager 改变选中项 viewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } }; }
6. MainActivity:
package com.android.drawernav; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends FragmentActivity { // 5.声明变量 private String[] data = {"选项一", "选项二", "选项三", "选项四", "选项五"}; private DrawerLayout drawerLayout; private ActionBarDrawerToggle toggle; private ListView listView; private FragmentManager fm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 6.初始化变量 drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // 设置抽屉的阴影 drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.END); // 显示 Home 按钮(此处显示抽屉把手) getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); fm = this.getSupportFragmentManager(); // 7.创建抽屉把手(看效果) // 参数:上下文,抽屉布局对象,把手菜单图标,打开抽屉的文本,关闭抽屉的文本 toggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_menu_white_24dp, R.string.drawer_open, R.string.drawer_close ) { // 抽屉打开 @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); // 重建选项菜单 invalidateOptionsMenu(); getActionBar().setTitle(getString(R.string.title)); } // 抽屉关闭 @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); // 重建选项菜单 invalidateOptionsMenu(); } }; // 同步状态(跟新 Home 位置显示的图标) toggle.syncState(); // 设置监听器 drawerLayout.setDrawerListener(toggle); // 9. 初始化左侧抽屉 initLeftDrawer(); } /** * 9.1 初始化左侧抽屉 */ private void initLeftDrawer() { listView = (ListView) findViewById(R.id.listView); // simple_list_item_1 一个控件 // simple_list_item_2 两个控件 // simple_list_item_activated_1 当选项选中时,改背景色(激活状态) ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_activated_1, data); // 设置单选模式 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setAdapter(adapter); // 9.2 点击左侧抽屉列表项的事件处理 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 设置标题 getActionBar().setTitle(data[position]); // 关闭抽屉 drawerLayout.closeDrawer(Gravity.START); // 10. 创建 XxxFragment & fragment_xxx.xml // 11. 使用 Fragment 替换 内容视图 FragmentTransaction ft = fm.beginTransaction(); switch (position) { case 0: ft.replace(R.id.content_layout, new MainFragment()); break; case 1: ft.replace(R.id.content_layout, new GameFragment()); break; default: // 3 / 4 / 5 (省略) break; } ft.commit(); } }); } // 8. 菜单 [看效果 - 重建选项菜单 invalidateOptionsMenu()] @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); // 8.1 搜索图标 显示/隐藏 // 左侧抽屉打开,隐藏操作栏按钮; // 左侧抽屉关闭,显示操作栏按钮 boolean isOpen = drawerLayout.isDrawerOpen(Gravity.START); menu.findItem(R.id.action_search).setVisible(!isOpen); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // 8.2 点击抽屉把手,打开/关闭抽屉 if (toggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } }