2012年4月20日金曜日

Android Up の振る舞いパターンを実装する

Android Design での Up と Back のガイドライン の振る舞いのパターンの実装方法を紹介します。

まず、Up と Back はそれぞれ次のように使い分けます。

・Up : 画面間の階層関係に基づいたアプリ内のナビゲーションに使う
・Back : 最近行った操作を逆時系列順にさかのぼるナビゲーションに使う

そのため、次のような違いがあります。

遷移範囲
・Up : アプリ内の遷移だけ
・Back : アプリ内だけでなく別のアプリやホームアプリにも遷移する

振る舞い
・Up : 画面遷移だけ
・Back : 画面遷移の他に、フローティングウィンドウ(ダイアログやポップアップ)のキャンセル、Action Mode のキャンセルや選択中のアイテムのキャンセル、ソフトキーボードを隠すなどの操作にも使われる


基本的には、Up は一つ上の階層に戻るナビゲーションに使います。


http://developer.android.com/design/media/navigation_up_vs_back_gmail.png

Up はアプリ内の遷移にだけ使うので、アプリのホーム画面には Up ボタンは置きません。



アプリ内のナビゲーション


・同じアプリの複数画面から遷移される画面での Up ボタンは、前の画面に戻るようにする

例えば、設定画面は同じアプリのいろいろな画面から遷移できるようになっていることが多いです。このようなエントリポイントがたくさんある画面では、Up ボタンが押されたときに前の画面に戻るようにします。つまり Back ボタンと同じ振る舞いです。

実装としては、finish() が適当でしょう。
@Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); }

・画面内での View の切り替えでは、Up や Back の振る舞いを変えない

例えば

  - タブやスワイプによる View の切り替え
  - ドロップダウンによる View の切り替え
  - リストのフィルタリング
  - リストのソート
  - ズームなどによる表示切り替え

ではアプリの階層は変わらず、Back 用のナビゲーション履歴も増えません。そのため、Up のナビゲーションをこの切り替えによって変更することはしません。



・同じ Activity 内での連続する画面の切り替えでは、Up や Back の振る舞いを変えない

Gmail アプリのように、リスト画面からその詳細画面に遷移し、そこからスワイプで前後のリストアイテムの詳細画面に遷移する場合も、アプリの階層は変わらず Back 用のナビゲーション履歴も増えません。

http://developer.android.com/design/media/navigation_between_siblings_gmail.png

この場合、詳細画面上の Up ボタンでは 1階層上に戻るようにします。リスト画面からしか詳細画面に遷移しないのであれば finish() でもいいでしょう。
Intent.FLAG_ACTIVITY_CLEAR_TOP と Intent.FLAG_ACTIVITY_SINGLE_TOP を組み合わせてstartActivity() を使ってリスト画面を呼び出す方法もあります。

ConversationDetails @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Intent intent = new Intent(this, ConversationList.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); return true; } return super.onOptionsItemSelected(item); }
いずれもスタック上は
| ConversationList |
 ↓ 
| ConversationList   ConversationDetails |
 ↓ Up
| ConversationList |

という動作になります。



・同じ階層の別 Activity への切り替えでは、Up の振る舞いを変えない

http://developer.android.com/design/media/navigation_between_siblings_market1.png

別 Activity への遷移のため、Back 用のナビゲーション履歴が増えます。しかし、アプリの階層として同じ位置にあたる画面なので、Up ボタンの振る舞いは Back と異なり一つ上の階層に遷移するようにします。

スタック上の動作としては次のようになります。
| BookList | 
 ↓
| BookList  Book1Details |
 ↓    ↓ Up
 ↓   | BookList |
 ↓ 
| BookList  Book1Details  Book2Details |
 ↓ Up
| BookList |

この場合、Book2Details の Up の振る舞いとして finish() は使えません。いずれも Intent.FLAG_ACTIVITY_CLEAR_TOP と Intent.FLAG_ACTIVITY_SINGLE_TOP を組み合わせてstartActivity() を使ってリスト画面を呼び出す方法がいいでしょう。


Book1Details
Book2Details @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Intent intent = new Intent(this, BookList.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); return true; } return super.onOptionsItemSelected(item); }
アプリの内部構成が複数のカテゴリにわかれていて、あるカテゴリの下の階層の画面から別のカテゴリの下の階層に遷移した場合、遷移先の画面の Up の振る舞いはそのカテゴリ内での1つ上の階層に戻ることです。

http://developer.android.com/design/media/navigation_between_siblings_market2.png

実装としては、次のように BookDetails と MovieDetails で startActivity() 先を変えます。


Book1Details
Book2Details @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Intent intent = new Intent(this, BookList.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); finish(); return true; } return super.onOptionsItemSelected(item); }
Movie1Details @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Intent intent = new Intent(this, MovieList.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); finish(); return true; } return super.onOptionsItemSelected(item); }
スタック上の動作はこうなります。

| Top  BookList | 
 ↓
| Top  BookList  Book1Details |
 ↓    ↓ Up
 ↓   | Top  BookList |
 ↓ 
| Top  BookList  Book1Details  Book2Details |
 ↓    ↓ Up
 ↓   | Top  BookList |
 ↓ 
| Top  BookList  Book1Details  Book2Details  Movie1Details |
 ↓ Up
| Top  BookList  Book1Details  Book2Details  MovieList |



ホープアプリ上のウィジェットやノーティフィケーションからのナビゲーション

ウィジェットやノーティフィーケションから遷移した画面上の Up ボタンの振る舞いは、次のようにします。

 ・同じアプリの特定の画面にいるときにノーティフィーケションから遷移した場合は、その特定の画面に戻る
 ・それ以外は、アプリのホーム画面(トップ画面)に遷移する


http://developer.android.com/design/media/navigation_from_outside_back.png

タスクのバックスタックにホーム画面を挿入しておくことで、Back ボタンでアプリを抜ける際に、どうやって遷移先の画面に移動したのかを忘れているユーザーをホーム画面にナビゲートすることができます。

API Level 11 から追加された PendingIntent#getActivities(Context context, int requestCode, Intent[] intents, int flags) を使うと、スタックに複数の Activity を挿入した状態にすることができます。

例えば、 public class WidgetProvider extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); Intent[] intents = new Intent[2]; intents[0] = new Intent(context, MainActivity.class); intents[1] = new Intent(context, MainActivity2.class); PendingIntent pendingIntent = PendingIntent.getActivities(context, 0, intents, Intent.FLAG_ACTIVITY_NEW_TASK); views.setOnClickPendingIntent(R.id.btn, pendingIntent); appWidgetManager.updateAppWidget(appWidgetIds, views); } } のようにすると

| ホームアプリ |
 ↓
 ↓ ホーム画面のウィジェットの R.id.btn をタップ
 ↓
| ホームアプリ | MainActivity  MainActivity2 |

のようになります。

そのため、ここで Back ボタンを押すと MainActivity に遷移します。

MainActivity2 を @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); finish(); return true; } return super.onOptionsItemSelected(item); } としておけば、Up を押した場合も MainActivity に遷移するようになります。

ちなみに MainActivity, MainActivity2 両方とも launchMode="standard" で、すでにアプリを起動している状態からだと

| ホームアプリ | MainActivity |
 ↓
| ホームアプリ | MainActivity  MainActivity2 |
 ↓ Home ボタン
| MainActivity  MainActivity2 | ホームアプリ |
 ↓
 ↓ ホーム画面のウィジェットの R.id.btn をタップ
 ↓
| ホームアプリ | MainActivity  MainActivity2 | MainActivity(2)  MainActivity2(2) | 

のようなスタックになります。

MainActivity だけ launchMode="singleTask" にすると

| ホームアプリ | MainActivity |
 ↓
| ホームアプリ | MainActivity  MainActivity2 |
 ↓ Home ボタン
| MainActivity  MainActivity2 | ホームアプリ |
 ↓
 ↓ ホーム画面のウィジェットの R.id.btn をタップ
 ↓
| ホームアプリ | MainActivity  MainActivity2(2) | 

のように MainActivity2 は新しく作られ、そこから Back で戻った先の MainActivity は以前のインスタンスになります。



・インダイレクトノーティフィケーション

複数のイベントの情報を同時に提供するときに、単一のノーティフィケーションを通知して、そこから複数のイベントについてまとめた中間画面(interstitial screen)にユーザーを導き、そこからイベントに対する処理や対応するアプリ画面への遷移を提供するスタイルをインダイレクトノーティフィケーション(indirect notifications)といいます。

中間画面で Back ボタンが押されたときは、間によけいなスタックを入れずノーティフィケーションを呼び出した画面に戻るようにします。
中間画面からアプリの対応する画面に遷移したら、そこから Back や Up ボタンでは上記と同じようにアプリのホーム画面(トップ画面)を経由するようにします。中間画面には戻りません。

http://developer.android.com/design/media/navigation_indirect_notification.png



・ポップアップノーティフィケーション

電話がかかってきたときや、Gtalk でビデオチャットの招待がきたときなど、すぐに対応するべき通知はポップアップで表示されることがあります(すぐに対応しなければならない通知以外では使ってはいけない)。

ポップアップノーティフィケーションでの Up / Back の振る舞いはインダイレクトノーティフィケーションと同じような感じで、ポップアップが出たときに Back でキャンセルするとポップアップが消え、ポップアップからアプリに遷移した後は、Up と Back ボタンではアプリのホーム画面(トップ画面)を経由するようにします。

http://developer.android.com/design/media/navigation_popup_notification.png




アプリ間のナビゲーション

Android の大きな特徴として別のアプリの画面をあたかも自分のアプリの続きのように遷移することができる、という点があります。

アプリA から アプリB のある画面を呼び出してその結果を受け取りたい場合は、アプリB のある画面(の Activity)を アプリA のタスク内で起動しなければなりません。 例えば、launchMode="singleTask" の Activity を startActivityForResult() で起動した場合、すぐに RESULT_CANCELED が返ってきてしまいます。

例えば、共有をサポートするアプリを呼び出した場合、

http://developer.android.com/design/media/navigation_between_apps_inward.png

呼び出し先の Activity は呼び出し元と同じタスクになります。

呼び出し先で Back ボタンが押されたり、呼び出し先での処理が完了して finish() した場合は、呼び出し元の画面に戻ります。

http://developer.android.com/design/media/navigation_between_apps_back.png

一方、呼び出し先で Up ボタンが押された場合、ユーザーは呼び出し先のアプリに留まりたいということなので、新しいタスクとして呼び出し先のホーム画面(もしくは1階層上の画面)に遷移するようにします。そこからさらに Back ボタンでアプリを終了した場合は、呼び出し元のアプリではなくホームアプリに戻るようにします。

http://developer.android.com/design/media/navigation_between_apps_up.png

最初の呼び出し元のタスクはバックグラウンドで保持され、ユーザーは後で Recent apps から戻ることができます。すでに呼び出し先のアプリ自身のタスクが走っている状態だと、Up ボタンが押された場合は、そのタスクが新しいタスクに置き換えられます。

新しくタスクを起動し、それ以外のタスクをホームアプリのバックグラウンドに移すには、次のように Intent.FLAG_ACTIVITY_NEW_TASK と Intent.FLAG_ACTIVITY_TASK_ON_HOME を組み合わせます。 @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); return true; } return super.onOptionsItemSelected(item); }


0 件のコメント:

コメントを投稿