实现APP高效导航
来源:互联网 发布:裙子太短 知乎 编辑:程序博客网 时间:2024/05/18 05:58
前言
今天看了Google教程上有关导航的知识,感觉只学到了一点。没有想象中的多。可能因为Google这个教程推出的时间比较早,所以当时的Android版本比较久,我打开官方示例项目的时候,有的API已经不推荐使用了。不过还是学到了一些,之前用到这么内容都是去网上搜,现在自己先真正学会一个简陋版复古版的导航,以后再来慢慢改善。
今天学到的内容:
- 将Tab和ViewPaper结合起来
- DrawerLayout的使用 侧边导航菜单
- 提供逻辑向上导航
- 提供合适的向后导航
- 实现后续导航
第一点和第二点侧重技术方面,后面三点都是更加侧重用户体验的细节处理
下面开始记录学习成果:
创建带有Tabs的滑动视图
结合ViewPaper和Tab的导航教程,实现相邻视图水平导航。
实现滑动视图
通过ViewPaper实现滑动视图,我们需要在XML中这么设置:
<?xml version="1.0" encoding="utf-8"?><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管理的每一页都是独立,这时我们最好的选择是使用Fragment,因为它可以提高性能。如果用独立的View来显示,可能会很吃内存。
使用Fragment来显示每一页我们要提供相应的adapter,这时有两种选择:
FragmentPagerAdapter:如果是固定的,少量的页数推荐这个
FragmentStatePagerAdapter:数量不确定时选它,它会销毁过去的Fragemnt来最小化内存开销。
public class CollectionDemoActivity extends FragmentActivity { // When requested, this adapter returns a DemoObjectFragment, // representing an object in the collection. DemoCollectionPagerAdapter mDemoCollectionPagerAdapter; ViewPager mViewPager; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_collection_demo); // ViewPager and its adapters use support library // fragments, so use getSupportFragmentManager. mDemoCollectionPagerAdapter = new DemoCollectionPagerAdapter( getSupportFragmentManager()); mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mDemoCollectionPagerAdapter); }}// Since this is an object collection, use a FragmentStatePagerAdapter,// and NOT a FragmentPagerAdapter.public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter { public DemoCollectionPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int i) { Fragment fragment = new DemoObjectFragment(); Bundle args = new Bundle(); // Our object is just an integer :-P args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1); fragment.setArguments(args); return fragment; } @Override public int getCount() { return 100; } @Override public CharSequence getPageTitle(int position) { return "OBJECT " + (position + 1); }}// Instances of this class are fragments representing a single// object in our collection.public static class DemoObjectFragment extends Fragment { public static final String ARG_OBJECT = "object"; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // The last two arguments ensure LayoutParams are inflated // properly. 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; }}
上面演示了如何添加滑动视图,下面是如何在此基础上添加Tab导航。
在Action Bar上添加Tab
为了实现在Action Bar上添加Tab,你应该把Action Bar的导航模式设置成 NAVIGATION_MODE_TABS ,然后创建对应ViewPaper页数数量的ActionBar.Tab,然后给每一个Tab都添加监听器。
@Overridepublic void onCreate(Bundle savedInstanceState) { final ActionBar actionBar = getActionBar(); ... // Specify that tabs should be displayed in the action bar. actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); // Create a tab listener that is called when the user changes tabs. ActionBar.TabListener tabListener = new ActionBar.TabListener() { public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // show the given tab } public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { // hide the given tab } public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { // probably ignore this event } }; // Add 3 tabs, specifying the tab's text and TabListener for (int i = 0; i < 3; i++) { actionBar.addTab( actionBar.newTab() .setText("Tab " + (i + 1)) .setTabListener(tabListener)); }}
在添加完Tab之后,我们要把ViewPaper的滑动和Tab相关联。
将滑动视图和Tab相关联
首先先考虑第一种情况,点击Tab,然后滑动视图导航到对应的页。要实现这个目的,我们想想,因为触发的是Tab的监听器,所以我们应该在Tab的监听器的相应方法里面去设置ViewPaper刺客应该显示哪一页。监听类ActionBar.TabListener()
@Overridepublic void onCreate(Bundle savedInstanceState) { ... // Create a tab listener that is called when the user changes tabs. ActionBar.TabListener tabListener = new ActionBar.TabListener() { public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // When the tab is selected, switch to the // corresponding page in the ViewPager. mViewPager.setCurrentItem(tab.getPosition()); } ... };}
同理,第二种情况:在滑动视图的时候改变Tab,滑动视图的时候触发ViewPaper的监听器,所以我们应该在ViewPaper的监听器的相应方法里改变Tab。监听类:ViewPager.OnPageChangeListener
@Overridepublic void onCreate(Bundle savedInstanceState) { ... mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { // When swiping between pages, select the // corresponding tab. getActionBar().setSelectedNavigationItem(position); } }); ...}
使用PagerTitleStrip而不是Tab
如果你不想用Tab导航,你可以用PagerTitleStrip来替换Tab,你只需要在XML文件里这么定义:
<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"> <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是完全嵌套在ViewPager里面的。
不过这些样式都有些老了,如果要在APP中使用,我们要考虑用新一点的样式去替代它们。
创建一个导航Drawer
它是一个隐藏在主屏幕左边的APP导航选项,一般都是用ListView的形式。当用户手指从屏幕左侧滑动,或是点击Action Bar上的icon,它就会显示出来。
创建一个Drawer布局
在UI的根目录下定义DrawerLayout布局,这个布局包括两个View,一个View是用来显示屏幕里的内容,此时导航Drawer看不到,另一个View就是用来显示Drawer的导航内容的。
下面这个例子,通过FrameLayout来显示屏幕内容,用ListView 来显示导航内容:
<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"> <!-- The main content view --> <FrameLayout android:id="@+id/content_frame" 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="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>
我们在写这个布局的时候应该要注意几点:
- 显示屏幕主要内容的View必须是第一个
- 显示屏幕主要内容的View应该充满父布局,因为它显示的时候Drawer要隐藏
- Drawer View应该使用android:layout_gravity来制定它的横向布局,为了更好的支持这个结构,值应该指定为start
- Drawer View的宽应该使用dp单位,高度匹配父布局。宽度不超过320dp,这样保证用户对屏幕内容始终可见
初始化Drawer列表
代码里的第一件事就应该是初始化Drawer列表,怎么做要看具体的APP要什么样的设计了。在这个例子里面,我们只要给ListView提供一个adapter就行了。
public class MainActivity extends Activity { private String[] mPlanetTitles; private DrawerLayout mDrawerLayout; private ListView mDrawerList; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPlanetTitles = getResources().getStringArray(R.array.planets_array); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); // Set the adapter for the list view mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles)); // Set the list's click listener mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); ... }}
在Drawer列表中的每一项,我们都应该有相应的响应事件,下面会将如何实现Drawer点击事件。
处理导航点击事件
当用户点击列表的时候,系统会回调 OnItemClickListener 监听器里的 onItemClick() 方法,这个方法具体怎么写,就要看你要怎么实现APP了。
下面的代码演示点击列表然后插入对应的Fragment,在实际的项目中不要插入Fragment,而应先看有无Fragment实例,否则会徒增内存消耗:
private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); }}/** Swaps fragments in the main content view */private void selectItem(int position) { // Create a new fragment and specify the planet to show based on position Fragment fragment = new PlanetFragment(); Bundle args = new Bundle(); args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); fragment.setArguments(args); // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment) .commit(); // Highlight the selected item, update the title, and close the drawer mDrawerList.setItemChecked(position, true); setTitle(mPlanetTitles[position]); mDrawerLayout.closeDrawer(mDrawerList);}@Overridepublic void setTitle(CharSequence title) { mTitle = title; getActionBar().setTitle(mTitle);}
监听Drawer的打开和关闭事件
除了监听Drawer的打开和关闭事件,你应该给DrawerLayout设置监听器,监听器应该实现DrawerLayout.DrawerListener接口,在这个接口里面包括了监听Drawer打开和关闭的方法- onDrawerOpened() 和 onDrawerClosed()。
除了实现这个接口,如果使用了Action Bar,我们可以继承ActionBarDrawerToggle这个类,因为这个类继承了 DrawerLayout.DrawerListener 接口,所以我们直接覆写打开和关闭的方法就好了。直接使用ActionBarDrawerToggle还方便了我们后面对点击Action Bar的Icon实现Drawer的打开和关闭功能的实现。
我们应该监听什么呢?当Drawer打开的时候,我们应该把Action Bar的内容修改成APP的标题,或是其他的什么;当Drawer关闭的时候,我们应该把Action Bar的内容修改成和内容相关的标题。还可以设置Action item在打开和关闭时的可见性。
public class MainActivity extends Activity { private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... mTitle = mDrawerTitle = getTitle(); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { super.onDrawerClosed(view); getActionBar().setTitle(mTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); getActionBar().setTitle(mDrawerTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); } /* 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); }}
通过APP图标来打开或关闭Drawer
用户可以用手指从左侧向右滑动来打开Drawer,如果使用的Action Bar,我们还可以实现点击Action Bar上的Icon来实现Drawer的开和关。通过之前提到的 ActionBarDrawerToggle 就能实现,代码几乎不用太大改变。
为了让ActionBarDrawerToggle工作,它需要五个参数
- 拥有Drawer的Activity
- DrawerLayout
- 用来指示打开关闭的drawable资源
- 用于描述打开Drawer的字符串
- 用于描述关闭Drawer的字符串
还有,要使用ActionBarDrawerToggle,我们必须在onPostCreate() 和 onConfigurationChanged()调用它:
public class MainActivity extends Activity { private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; ... public void onCreate(Bundle savedInstanceState) { ... mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description */ R.string.drawer_close /* "close drawer" description */ ) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { super.onDrawerClosed(view); getActionBar().setTitle(mTitle); } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); getActionBar().setTitle(mDrawerTitle); } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); } @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); mDrawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Pass the event to ActionBarDrawerToggle, if it returns // true, then it has handled the app icon touch event if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle your other action bar items... return super.onOptionsItemSelected(item); } ...}
提供向上返回按钮
这里的向上返回分为两种,一种我之前写过,是返回APP的逻辑上级,可以看这一篇博客,第二种是返回其他的APP(Activity可能由其他APP启动)。
向上返回逻辑Activity
请看这一篇博客
从新的返回堆栈中向上 导航
如果你的APP提供了 intent filters 允许其他APP启动的话,你就应该在 onOptionsItemSelected() 方法里考虑到这一种情况:如果Activity由其他APP启动,那当用户点击向上返回按钮的时候,你应该添加一个新的返回堆栈,防止用户一点击,你的APP就结束了。
你应该先调用 shouldUpRecreateTask()方法,检查Activity实例是都在其他APP的任务里,如果返回true,你就通过TaskStackBuilder新建一个任务堆栈。如果返回fasle,调用navigateUpFromSameTask() 返回最近的一个Parent Activity就行啦。
@Overridepublic boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // Respond to the action bar's Up/Home button case android.R.id.home: Intent upIntent = NavUtils.getParentActivityIntent(this); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // This activity is NOT part of this app's task, so create a new task // when navigating up, with a synthesized back stack. TaskStackBuilder.create(this) // Add all of this activity's parents to the back stack .addNextIntentWithParentStack(upIntent) // Navigate up to the closest parent .startActivities(); } else { // This activity is part of this app's task, so simply // navigate up to the logical parent activity. NavUtils.navigateUpTo(this, upIntent); } return true; } return super.onOptionsItemSelected(item);}
注意:为了让addNextIntentWithParentStack()方法有效,你应该在manifest文件里声明每一个Activity的逻辑父Activity,通过android:parentActivityName (兼容更低版本的话就使用 < meta-data >,前面博客提到,不再累述)。
提供合适的向后导航
这里的内容比较琐碎分这几块:
- 从通知等途径直接开启你的APP
- 为Fragment提供向后导航
- 为WebView提供向后导航
第一点在上面就提到了,这里就不再说了,如果想看这一部分详细讲解的朋友,这一篇官方教程。
为Fragment提供向后导航
如果Fragment的模式是摘要和细节这样的模式的话,在替换Fragment的时候,之前的Fragment应该被加入返回堆栈中。比如说,在新闻阅读器里面,一个Fragment显示新闻标题,另一个Fragment显示新闻内容,点击对应的新闻标题后进入显示新闻内容的Fragment,为了在用户看完新闻后点击返回键不要直接返回手机桌面,我们应该把显示新闻标题的Fragment加入返回堆栈中,这样点击返回键后就会返回新闻标题的Fragment,而不会出现用户意料之外的退出了。我们通过调用 addToBackStack()来实现:
// Works with either the framework FragmentManager or the// support package FragmentManager (getSupportFragmentManager).getSupportFragmentManager().beginTransaction() .add(detailFragment, "detail") // Add this transaction to the back stack .addToBackStack() .commit();
如果你想修改其他的一些UI元素,来显示你现在Fragment的状态,你应该在加入返回堆栈之后更新UI。至于为什么我也不太懂,官方这么让我们去做的。通过添加 FragmentManager.OnBackStackChangedListener监听器,在加入堆栈后,改变UI。
getSupportFragmentManager().addOnBackStackChangedListener( new FragmentManager.OnBackStackChangedListener() { public void onBackStackChanged() { // Update your UI here. } });
为webview实现向后导航
如果webview来访问网页,通常我们应该事先webview的向后导航,比如用户在webview上连续进入很多网站,点击返回键的时候应该返回上一个网页,而不是直接结束网页浏览。
@Overridepublic void onBackPressed() { if (mWebView.canGoBack()) { mWebView.goBack(); return; } // Otherwise defer to system default behavior. super.onBackPressed();}
如果会产生很多的历史记录,那么我们应该考虑周全,否则用户就觉得退出你的APP很费劲。
好了,今天的内容就这么多了。
- 实现APP高效导航
- APP导航实现
- Mono for Android 实现高效的导航
- 借助于百度导航App,实现一个轻量级的百度导航
- 简单高效的实现Android App全局字体替换
- 简单高效的实现Android App全局字体替换
- 简单高效的实现Android App全局字体替换
- (八十一)利用系统自带App来实现导航
- Android调用百度地图app , 实现百度定位、导航
- Android App之底部tab导航常用实现方案总结
- app实现状态栏和导航栏沉浸效果
- Android App应用底部导航栏实现的一种方式
- Android App底部导航栏的另一种实现方式
- 调起地图App实现路径规划导航等
- “fullLoad” app(二)之底部导航功能实现
- 从零开始搭建app—首页导航栏实现(一)
- ViewPager封装工具类: 轻松实现APP导航或APP中的广告栏
- APP开发:导航界面
- Oracle -- Grant privilege
- 中文分词技术(中文分词原理)
- linux性能监控和优化命令 top
- oracle实现主键自增
- 使用Dreamweaver去掉Bom头
- 实现APP高效导航
- linux性能监控和优化命令 free
- 控制文件的备份
- Android Studio如何安装插件?
- 软件测试常考面试题-软件测试面试宝典
- (Educational Codeforces Round 9)Magic Matrix(最小生成树)★
- TCP 三次握手连接&四次握手断开
- mysql 获取昨天日期、今天日期、明天日期以及前一个小时和后一个小时的时间
- linux性能监控和优化命令 vmstat