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
    • ViewPagerPagerTitleStripFragmentPagerAdapterFragmentStatePagerAdapter
  • 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)可以为通知栏消息设置进度条,支持实时进度、持续模式
  • Supporting Swipe-to-Refresh

  • 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来进行过滤
  • 优化app的assistant内容

  • app link,让自己的app成为自己网站uri的默认打开方式

results matching ""

    No results matching ""