Best Practices for Interaction and Engagement
Designing Effective Navigation
- Planning Screens and Their Relationships
- 实体关系图(Entity-relationship diagram),数据、用户之间的关系、操作
- 详尽的用例场景
- 界面关系图
- Planning for Multiple Touchscreen Sizes
- 界面组合技术,在平板电脑、电视上,屏幕很大,可以把列表界面和详情界面左右排布一起显示
- Providing Descendant and Lateral Navigation
- Descendant,父界面前往子界面
- Lateral,兄弟界面之间的跳转
- list和section
- Providing Ancestral and Temporal Navigation
- Temporal(时间性),按下返回键,应该回到上一界面
- Ancestral(父子性),ActionBar/ToolBar上的返回按钮,返回父界面(通常是上一界面,但并不全是,容纳WebView的Activity就是很好的例子),需要注意的是,一定要清除backstack
- ...
Implementing Effective Navigation
- Creating Swipe Views with Tabs
ViewPager
,PagerTitleStrip
,FragmentPagerAdapter
,FragmentStatePagerAdapter
- Creating a Navigation Drawer
- drawer layout里面可以显式一个菜单列表
ActionBarDrawerToggle
(appcompat-v7中)可以监听drawer的开启与关闭事件,也可以用代码控制drawer的开启与关闭- 完整样例
Providing Up Navigation
- 首先在manifest里面为activity声明其parent activity,用于up navigation
- 然后在
onOptionsItemSelected
回调中处理up navigation:
@Override public 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); }
Providing Proper Back Navigation
- 安卓系统都有一个物理返回键,所以不应该在UI上额外添加一个返回键
- 安卓系统的back stack通常情况下都可以应对back navigation
但是以下情况需要特殊考虑
- 从通知栏消息、widget、navigation drawer直接进入一个深层次的activity
// Intent for the activity to open when user selects the notification Intent detailsIntent = new Intent(this, DetailsActivity.class); // Use TaskStackBuilder to build the back stack and get the PendingIntent PendingIntent pendingIntent = TaskStackBuilder.create(this) // add all of DetailsActivity's parents to the stack, // followed by DetailsActivity itself .addNextIntentWithParentStack(upIntent) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setContentIntent(pendingIntent);
- fragment之间的导航
// 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();
需要注意的是,当fragment是在ViewPager中水平切换时,不应该把transaction 加入到 backstack中。
- WebView
@Override public void onBackPressed() { if (mWebView.canGoBack()) { mWebView.goBack(); return; } // Otherwise defer to system default behavior. super.onBackPressed(); }
Implementing Descendant Navigation
- 通常都是
Intent
加上startActivity(intent)
,或者FragmentTransaction
来完成向子界面的导航 - 需要注意的是,如果app将打开其他app的界面,为了防止用户中途离开,再次从launcher打开app时,却是其他app的界面,可以为intent设置
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
flag
Intent externalActivityIntent = new Intent(Intent.ACTION_PICK); externalActivityIntent.setType("image/*"); externalActivityIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(externalActivityIntent);
- 通常都是
Notifying the User
- Building a Notification
- 使用
NotificationCompat.Builder
创建通知栏消息 - 响应用户对通知栏消息的点击,启动相应Activity,同时要考虑是否提供返回的导航设计
- 使用
- Preserving Navigation when Starting an Activity
- 点击通知栏消息,可能会启动两种类型的Activity:正常使用流程会启动的Activity;仅仅是把通知栏消息展开的Activity;
- 前者通常需要提供back导航支持,在manifest中声明Activity父子关系,同时构造PendingIntent时构造back stack,使用
stackBuilder.getPendingIntent
创建PendingIntent - 后者通常不需要加入到back stack中,无需手动构造back stack,使用
PendingIntent.getActivity
创建PendingIntent,同时在manifest中设置以下选项:<activity android:name=".ResultActivity" ... android:launchMode="singleTask" android:taskAffinity="" android:excludeFromRecents="true"> </activity>
完整示例:
Intent resultIntent = new Intent(this, ResultActivity.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Adds the back stack stackBuilder.addParentStack(ResultActivity.class); // Adds the Intent to the top of the stack stackBuilder.addNextIntent(resultIntent); // Gets a PendingIntent containing the entire back stack PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.notification_icon) .setContentTitle("My notification") .setContentText("Hello World!") .setContentIntent(resultPendingIntent); // Sets an ID for the notification int mNotificationId = 001; // Gets an instance of the NotificationManager service NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); // Builds the notification and issues it. mNotifyMgr.notify(mNotificationId, mBuilder.build());
Updating Notifications
- 创建通知栏消息时,如果指定了id,以后可以通过id对已经显示的消息进行修改/更新、移除操作;
- 修改/更新只需以相同的id,新的
Notification
对象,调用mNotifyMgr.notify
即可; - 如果消息支持被移除,则用户可以手动移除(单个或者所有),
setAutoCancel()
会让消息在用户点击时自动消失;cancel(id)
和cancelAll()
可以代码移除通知栏消息;
Using Big View Styles
- Android 4.1之后,通知栏消息引入了action的支持,方便用户快捷操作
// Sets up the Snooze and Dismiss action buttons that will appear in the // big view of the notification. Intent dismissIntent = new Intent(this, PingService.class); dismissIntent.setAction(CommonConstants.ACTION_DISMISS); PendingIntent piDismiss = PendingIntent.getService(this, 0, dismissIntent, 0); Intent snoozeIntent = new Intent(this, PingService.class); snoozeIntent.setAction(CommonConstants.ACTION_SNOOZE); PendingIntent piSnooze = PendingIntent.getService(this, 0, snoozeIntent, 0); // Constructs the Builder object. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_notification) .setContentTitle(getString(R.string.notification)) .setContentText(getString(R.string.ping)) .setDefaults(Notification.DEFAULT_ALL) // requires VIBRATE permission /* * Sets the big view "big text" style and supplies the * text (the user's reminder message) that will be displayed * in the detail area of the expanded notification. * These calls are ignored by the support library for * pre-4.1 devices. */ .setStyle(new NotificationCompat.BigTextStyle() .bigText(msg)) .addAction (R.drawable.ic_stat_dismiss, getString(R.string.dismiss), piDismiss) .addAction (R.drawable.ic_stat_snooze, getString(R.string.snooze), piSnooze);
Displaying Progress in a Notification
setProgress (int max, int progress, boolean indeterminate)
可以为通知栏消息设置进度条,支持实时进度、持续模式
- Building a Notification
Supporting Swipe-to-Refresh
- 官方有SwipeRefreshLayout
- 民间有Ultra Pull To Refresh
- 滑到底部触发加载更多可以使用Mugen
Adding Search Functionality
在App Bar中添加SearchView,注意它有support版本
res/menu/options_menu.xml:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/search" android:title="@string/search_title" android:icon="@drawable/ic_search" android:showAsAction="collapseActionView|ifRoom" android:actionViewClass="android.widget.SearchView" /> </menu>
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.options_menu, menu); return true; }
创建可搜索的配置
res/xml/searchable.xml,配置SearchView的label,hint
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_name" android:hint="@string/search_hint" />
manifest
<activity ... > ... <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity>
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.options_menu, menu); // Associate searchable configuration with the SearchView SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView(); searchView.setSearchableInfo( searchManager.getSearchableInfo(getComponentName())); return true; }
当用户提交一个query时,SearchView会发出一个
ACTION_SEARCH
intent,需要在manifest文件中声明响应此intent
Making Your App Content Searchable by Google
- Google对如何优化对app内的内容、网站的搜索,提供了相应的建议
- Deep Links,通过在manifest中声明感兴趣的Intent,可以在用户在其他app内触发此intent时启动自己的app,intent可以设置action, category, scheme来进行过滤