Google示例APP,教你实现Tab+ViewPaper,教你实现Drawer导航
来源:互联网 发布:python正则表达式匹配 编辑:程序博客网 时间:2024/05/21 00:49
前言
昨天看了Google教程-实现有效的导航之后,也看了看示例APP的源码,没有什么特别的难点,不过在此自己记录一下所学的知识,这样以后就不用再上网找了。
通过学习Drawer和ToolBar的使用,我终于能看懂在Android Studio中的有个叫Nevigation Drawer的起始项目了。
这次分别记录两个APP,一个是Tab+ViewPaper,一个是Drawer导航。
正文
Tab + ViewPaper,实现带有Tab的滑动视图
运行效果:
首先是ViewPaper + Tab
然后是ViewPaper + PagerTitleStrip
两种效果一样,不过第二种没有点击标题然后切换视图的效果,而且样式也不太好。
先看第一种导航的布局文件:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" />
只有一个viewpaper
再看它的代码:
public class MainActivity extends FragmentActivity implements ActionBar.TabListener { AppSectionsPagerAdapter mAppSectionsPagerAdapter; ViewPager mViewPager; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create the adapter that will return a fragment for each of the three primary sections // of the app. mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager()); // Set up the action bar. final ActionBar actionBar = getActionBar(); // Specify that the Home/Up button should not be enabled, since there is no hierarchical // parent. actionBar.setHomeButtonEnabled(false); // Specify that we will be displaying tabs in the action bar. actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); // Set up the ViewPager, attaching the adapter and setting up a listener for when the // user swipes between sections. mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mAppSectionsPagerAdapter); mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { // When swiping between different app sections, select the corresponding tab. // We can also use ActionBar.Tab#select() to do this if we have a reference to the // Tab. actionBar.setSelectedNavigationItem(position); } }); // For each of the sections in the app, add a tab to the action bar. for (int i = 0; i < mAppSectionsPagerAdapter.getCount(); i++) { // Create a tab with text corresponding to the page title defined by the adapter. // Also specify this Activity object, which implements the TabListener interface, as the // listener for when this tab is selected. actionBar.addTab( actionBar.newTab() .setText(mAppSectionsPagerAdapter.getPageTitle(i)) .setTabListener(this)); } } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { } @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { // When the given tab is selected, switch to the corresponding page in the ViewPager. mViewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { } /** * A {@link FragmentPagerAdapter} that returns a fragment corresponding to one of the primary * sections of the app. */ public static class AppSectionsPagerAdapter extends FragmentPagerAdapter { public AppSectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int i) { switch (i) { case 0: // The first section of the app is the most interesting -- it offers // a launchpad into the other demonstrations in this example application. return new LaunchpadSectionFragment(); default: // The other sections of the app are dummy placeholders. Fragment fragment = new DummySectionFragment(); Bundle args = new Bundle(); args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, i + 1); fragment.setArguments(args); return fragment; } } @Override public int getCount() { return 3; } @Override public CharSequence getPageTitle(int position) { return "Section " + (position + 1); } }
这是和第一种导航有关的全部代码,我们看到创建一个ViewPaper的适配器,这个适配器继承FragmentPagerAdapter来为ViewPaper填充内容,然后ViewPaper设置了滑动改变的监听器,这个监听器的作用是,当用户滑动内容是,让Tab跟着变。
为了与之对应,当用户点击Tab的时候,ViewPaper的内容当让也要变。所以我们还要继承ActionBar.TabListener这个接口,在onTabSelected()方法里设置当前显示哪一个内容。
当我们使用Tab导航的时候,不要忘了设置Tab导航模式是actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
第二种导航,先看布局文件:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- This title strip will display the currently visible page title, as well as the page titles for adjacent pages. --> <android.support.v4.view.PagerTitleStrip android:id="@+id/pager_title_strip" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:background="#33b5e5" android:textColor="#fff" android:paddingTop="4dp" android:paddingBottom="4dp" /></android.support.v4.view.ViewPager>
PagerTitleStrip嵌套在ViewPaper,它们都是v4包下的。
然我们再来看看它们对应的代码:
public class CollectionDemoActivity extends FragmentActivity { DemoCollectionPagerAdapter mDemoCollectionPagerAdapter; ViewPager mViewPager; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_collection_demo); // Create an adapter that when requested, will return a fragment representing an object in // the collection. // // ViewPager and its adapters use support library fragments, so we must use // getSupportFragmentManager. mDemoCollectionPagerAdapter = new DemoCollectionPagerAdapter(getSupportFragmentManager()); // Set up action bar. final ActionBar actionBar = getActionBar(); // Specify that the Home button should show an "Up" caret, indicating that touching the // button will take the user one step up in the application's hierarchy. actionBar.setDisplayHomeAsUpEnabled(true); // Set up the ViewPager, attaching the adapter. mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mDemoCollectionPagerAdapter); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // This is called when the Home (Up) button is pressed in the action bar. // Create a simple intent that starts the hierarchical parent activity and // use NavUtils in the Support Package to ensure proper handling of Up. Intent upIntent = new Intent(this, MainActivity.class); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // This activity is not part of the application's task, so create a new task // with a synthesized back stack. TaskStackBuilder.from(this) // If there are ancestor activities, they should be added here. .addNextIntent(upIntent) .startActivities(); finish(); } else { // This activity is part of the application's task, so simply // navigate up to the hierarchical parent activity. NavUtils.navigateUpTo(this, upIntent); } return true; } return super.onOptionsItemSelected(item); } /** * A {@link android.support.v4.app.FragmentStatePagerAdapter} that returns a fragment * representing an object in the collection. */ public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter { public DemoCollectionPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int i) { Fragment fragment = new DemoObjectFragment(); Bundle args = new Bundle(); args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1); // Our object is just an integer :-P fragment.setArguments(args); return fragment; } @Override public int getCount() { // For this contrived example, we have a 100-object collection. return 100; } @Override public CharSequence getPageTitle(int position) { return "OBJECT " + (position + 1); } } /** * A dummy fragment representing a section of the app, but that simply displays dummy text. */ public static class DemoObjectFragment extends Fragment { public static final String ARG_OBJECT = "object"; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false); Bundle args = getArguments(); ((TextView) rootView.findViewById(android.R.id.text1)).setText( Integer.toString(args.getInt(ARG_OBJECT))); return rootView; } }}
它的整体结构是viewpaper里面显示fragment,通过自己编写的 DemoCollectionPagerAdapter 类作为viewpaper的适配器,给viewpaper提供相应的内容,我们看到,应为DemoCollectionPagerAdapter 继承了FragmentStatePaperAdapter,所以我们不用担心每次都新建Fragment实例,用给系统内存带来影响,因为它会把不显示的Fragment自动销毁掉,所以达到最小化内存的使用。
第二种导航中除了对功能性,我们还注意到
actionBar.setDisplayHomeAsUpEnabled(true);
通过这个设置,我们可以点击这个Activity的ActionBar上的Icon来返回层次逻辑上的上一个Activity,不过还有文章可以说
在Activity覆写的onOptionsItemSelected()方法里面,官方教程考虑到了这个情况,如果这个Activity是从别的APP启动,那么点击ActionBar上的Icon,会出现不能返回的情况。因为这时这个Activity所在APP的返回堆栈是空的,所以点击返回键以后没用。
一般用户都期望在这时点击ActionBar上的Icon,应该返回这个Activity的逻辑上层,所以我们如果检查到这种情况,应该在这个APP的返回堆栈中先添加这个Activity的所有父Activity,也就是逻辑上层Activity:
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // This is called when the Home (Up) button is pressed in the action bar. // Create a simple intent that starts the hierarchical parent activity and // use NavUtils in the Support Package to ensure proper handling of Up. Intent upIntent = new Intent(this, MainActivity.class); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // This activity is not part of the application's task, so create a new task // with a synthesized back stack. TaskStackBuilder.from(this) // If there are ancestor activities, they should be added here. .addNextIntent(upIntent) .startActivities(); finish(); } else { // This activity is part of the application's task, so simply // navigate up to the hierarchical parent activity. NavUtils.navigateUpTo(this, upIntent); } return true; } return super.onOptionsItemSelected(item); }
关于这块的具体内容可一看上一篇博客。
对了,NavUtils.navigateUpTo(this, upIntent);这个方法通过代码来确定上层逻辑是谁,我们也可以在Manifest问津里通过xml文件来制定,然后直接把这个点击交给onOptionsItemSelected()的超类来处理就好。
还有一个和小神奇的事情,我在一个Fragment的代码看到:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_section_dummy, container, false); Bundle args = getArguments(); ((TextView) rootView.findViewById(android.R.id.text1)).setText( getString(R.string.dummy_section_text, args.getInt(ARG_SECTION_NUMBER))); return rootView; }
这个setText()里面竟然带了两个参数,我马上看了一下id是dummy_section_text的字符串:
<string name="dummy_section_text">Section %1d is just a dummy section.</string>
原来是这样,第二个参数来填上面这个%1d,瞬间有一种C语言的感觉有没有。
Drawer导航,实现侧边导航
先来看看效果:
大概是这个效果,除了这个意外,还要考虑点击ActionBar展开关闭侧边导航,以及ActonBar名字的改变(在Drawer打开和关闭的时候)Action Item的显示和隐藏。
好了,我们先看一下主Activity的布局文件:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#111"/></android.support.v4.widget.DrawerLayout>
注意了使用Drawer导航就要像这样使用XML文件,第一个View一定是显示主布局的View,通常会充满父布局,第二个View是Drawer导航View,这里是一个ListView,也可以是其他的Android组件。这里我们设置第二个View的android:layout_gravity属性为start,这样保证了RTL布局中它会在右边,LTR布局中它会在左侧。
对了,Drawer的高度要匹配父布局,宽度单位要用dp,不能超过320dp,保证用户始终能看到显示主内容的视图。
让我们看一下代码,因为只有一个Java文件,我就直接贴出来了:
public class MainActivity extends Activity { private DrawerLayout mDrawerLayout; private ListView mDrawerList; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; private String[] mPlanetTitles; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTitle = mDrawerTitle = getTitle(); mPlanetTitles = getResources().getStringArray(R.array.planets_array); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); // set a custom shadow that overlays the main content when the drawer opens mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); // set up the drawer's list view with items and click listener mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles)); mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); // enable ActionBar app icon to behave as action to toggle nav drawer getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); // ActionBarDrawerToggle ties together the the proper interactions // between the sliding drawer and the action bar app icon mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description for accessibility */ R.string.drawer_close /* "close drawer" description for accessibility */ ) { public void onDrawerClosed(View view) { getActionBar().setTitle(mTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } public void onDrawerOpened(View drawerView) { getActionBar().setTitle(mDrawerTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; mDrawerLayout.setDrawerListener(mDrawerToggle); if (savedInstanceState == null) { selectItem(0); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } /* Called whenever we call invalidateOptionsMenu() */ @Override public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // The action bar home/up action should open or close the drawer. // ActionBarDrawerToggle will take care of this. if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle action buttons switch(item.getItemId()) { case R.id.action_websearch: // create intent to perform web search for this planet Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.putExtra(SearchManager.QUERY, getActionBar().getTitle()); // catch event that there's no activity to handle intent if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show(); } return true; default: return super.onOptionsItemSelected(item); } } /* The click listner for ListView in the navigation drawer */ private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } private void selectItem(int position) { // update the main content by replacing fragments Fragment fragment = new PlanetFragment(); Bundle args = new Bundle(); args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); fragment.setArguments(args); FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // update selected item and title, then close the drawer mDrawerList.setItemChecked(position, true); setTitle(mPlanetTitles[position]); mDrawerLayout.closeDrawer(mDrawerList); } @Override public void setTitle(CharSequence title) { mTitle = title; getActionBar().setTitle(mTitle); } /** * When using the ActionBarDrawerToggle, you must call it during * onPostCreate() and onConfigurationChanged()... */ @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass any configuration change to the drawer toggls mDrawerToggle.onConfigurationChanged(newConfig); } /** * Fragment that appears in the "content_frame", shows a planet */ public static class PlanetFragment extends Fragment { public static final String ARG_PLANET_NUMBER = "planet_number"; public PlanetFragment() { // Empty constructor required for fragment subclasses } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_planet, container, false); int i = getArguments().getInt(ARG_PLANET_NUMBER); String planet = getResources().getStringArray(R.array.planets_array)[i]; int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()), "drawable", getActivity().getPackageName()); ((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId); getActivity().setTitle(planet); return rootView; } }}
基本上对于每行代码官方都给出了简练的解释,但是我还是简单记录一下重点吧,嘻嘻:
首先第一件事当然是给Drawer导航提供内容,因为这个Drawer导航是一个ListView,所以我们要提供适配器,以及一个用来监听Item点击事件的监听器:
mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles)); mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
这个相关代码比较简单,大家可以自己会看上面的代码,主要就是用数组填充了ListView,然后点击每一项可以切换相应的Fragment。
然后看一下重点,ActionBarDrawerToggle,这类帮我们解决了很多问题,因为我们要监听Drawer导航的打开和关闭事件,那么我们就要继承 DrawerLayout.DrawerListener,然后实现 onDrawerOpened() 和 onDrawerClosed()方法,因为ActionBarDrawerToggle继承了这个接口,所以我们只要覆写方法就行了,除了这个,它还可以顺道帮我们解决点击ActionBar上的Icon然后实现Drawer的打开和关闭,是不是很方便。
当然是用ActionBarDrawerToggle,一定不能忘记去覆写这两个方法onPostCreate()和onConfigurationChanged()。
mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description for accessibility */ R.string.drawer_close /* "close drawer" description for accessibility */ ) { public void onDrawerClosed(View view) { getActionBar().setTitle(mTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } public void onDrawerOpened(View drawerView) { getActionBar().setTitle(mDrawerTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; mDrawerLayout.setDrawerListener(mDrawerToggle);
通过监听打开和关闭事件,完成了ActionBar改变标题的功能,除了这个,还有invalidateOptionsMenu()方法,它只要一出现,系统就会回调onPrepareOptionsMenu()方法,那有什么用呢,我们可以在这个里面,设置Action Item的可见性呀,效果提里的放大镜注意到了么,正常状态下它会显示,但是打开的时候它是看不见的。我们看看onPrepareOptionsMenu()的相关代码:
public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); }
上面提到的点击Icon打开和关闭Drawer导航怎么实现呢?ActionBarDrawerToggle会自动帮我们处理这个事件,它的内部已经封装好了怎么处理这个时间,我们只要在onOptionsItemSelected()方法里去捕捉到返回true,表示已处理就行了:
@Override public boolean onOptionsItemSelected(MenuItem item) { // The action bar home/up action should open or close the drawer. // ActionBarDrawerToggle will take care of this. if (mDrawerToggle.onOptionsItemSelected(item)) { return true; }
其实上面的代码已经注释的很好了,你完全可以直接看代码和注释,不用看我。
还有一个小细节,在这个例子里面Drawer是一个ListView,我们给它的监听器是这样的:
private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } }private void selectItem(int position) { // update the main content by replacing fragments Fragment fragment = new PlanetFragment(); Bundle args = new Bundle(); args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); fragment.setArguments(args); FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // update selected item and title, then close the drawer mDrawerList.setItemChecked(position, true); setTitle(mPlanetTitles[position]); mDrawerLayout.closeDrawer(mDrawerList); }
通过selectItem()来实现点击显示相应的Fragment,但是selectItem()的最后三行不能忘,点击后通过 mDrawerList.setItemChecked(position, true); 来显示ListView里面Item的选中状态,更新ActionBar的标题,最后通过调用mDrawerLayout.closeDrawer(mDrawerList);关闭Drawer导航。
好了,今天的内容就是这么多
- Google示例APP,教你实现Tab+ViewPaper,教你实现Drawer导航
- Android 原生导航 IV-实现Drawer+ToolBar+Tab
- 使用活动条ActionBar---实现Tab以及下拉式导航---导航方便你我他
- 手把手教你用Jquery实现简单tab
- Google示例APP,教你如何写出适配多种屏幕的新闻阅读器
- 教你轻松几步实现底部导航栏
- 手把手教你炫酷慕课网视频启动导航的完美实现
- 手把手教你炫酷慕课网视频启动导航的完美实现
- 手把手教你TabLayout、ViewPager、Fragment实现顶部导航
- 手把手教你炫酷慕课网视频启动导航的完美实现
- 手把手教你快速实现Android底部导航栏
- 教你如何使用 Google App Engine
- 教你如何使用 Google App Engine
- ActionBar实现Tab导航
- Tab导航实现
- ActivityGroup实现Tab导航
- Android App之底部tab导航常用实现方案总结
- 教你实现Android下划线能滑动的Tab标签页
- Codeforces Round #228 (Div. 2) E Fox and Card Game(贪心博弈)
- 我排第几 第几是谁? 康托展开与逆康托展开
- kafka实践(四):kafka使用之中的一些关注点
- go语言数组
- 基于tensorflow的MNIST手写数字识别
- Google示例APP,教你实现Tab+ViewPaper,教你实现Drawer导航
- iOS点击链接跳转到App Store上的应用内
- RecyclerView的使用
- 带你玩转Visual Studio——调用约定与(动态)库
- 第十一章 ICMP:Internet控制报文协议
- sql查询语句
- 【十】DOM(一)
- java实现归并排序
- 餐馆(动态规划)