Upボタンの実装メモ
はじめに
Upボタンの実装サンプルとかあまり詳しく書いてないので自分が実装したときのメモをまとめた。 正確性にあまり自信がない。
Upボタン
ActionBarの左端にはUpボタンと呼ばれる上位画面へ遷移するための機能がある。 ナビゲーションバーのBackボタンとは機能的にかなり異なる。 具体的な違いはAndroidDevelopersサイトの図を見るのが一番わかりやすいと思う。 http://developer.android.com/design/patterns/navigation.html
Upボタンの実装は難しい
Upボタンの実装のために、API level 16からActivity
にいくつかのメソッドが追加され、SupportLibrary
にも同様の機能を持つNavUtils
が追加された。
AndroidDevelopersサイトにもUp動作の実装例としてソースコードが記載されている。
http://developer.android.com/training/implementing-navigation/ancestral.html
しかし、ソースコード通りにUpボタンを実装してみても思ったとおりに動かないと感じることが多いかった。 この記事ではUpボタンの実装時に躓いたポイントと、その対処法をいくつか纏めることにする。
IllegalArgumentException
Upボタンを押した時、以下の様な例外が発生することがある。
java.lang.IllegalArgumentException: Activity ACTIVITY_NAME does not have a parent activity name specified. (Did you forget to add the android.support.PARENT_ACTIVITY <meta-data> element in your manifest?)
これは親アクティビティの指定漏れ。
親アクティビティの指定はJellyBean
以降のためのandroid:parentActivityName
属性と、それ以前のための<meta-data>
の2箇所にわけて記載する必要がある。
<activity android:name=".ChildActivity" android:parentActivityName=".ParentActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".ParentActivity" /> </activity>
親アクティビティが再生成される
おそらく、Upボタン実装の難易度を高く感じさせているのはこの挙動だと思う。
サンプルコードの通りAndroidManifest.xml
にPARENT_ACTIVITY
の指定をして、以下のようにUpボタンの実装を行ったとする。
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask(this); return true; } return super.onOptionsItemSelected(item); }
この状態で親Activityから子Activityへ遷移し、子ActivityでUpボタンを押下するとActivity#finish()
して親Acitivityに戻るのではなく、全く新しい親Activityに遷移する。
このとき、upintent
にはFLAG_ACTIVITY_CLEAR_TOP
が含まれているため、親Activityが再生成されたように見えてしまう。
これは正常な動作だが、感覚的には親→子と移動したのだからひとつ前の親に戻ってほしいと思うのが普通だ。
解決方法はいくつかあるが、親ActivityのlaunchMode
をsingleTop
にするのが一番簡単。
Java側のソースコードは修正する必要がない。
<activity android:name=".ChildActivity" android:launchMode="singleTop" android:parentActivityName=".ParentActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".ParentActivity" /> </activity>
android:launchMode="singleTop"
を指定した場合、スタックの最上位が親Activityの場合はインスタンスが再利用され、Activity#onCreate()
の代わりにActivity#onNewIntent()
が呼ばれることになる。
singleTopを避けたい場合
singleTop
を指定できない、つまり親アクティビティが複数生成される可能性がある場合というのは、パラメータによって親Activityの内容が変わることが大半だ。
その場合、NavUtils.navigateUpFromSameTask()
しただけではもともとパラメータが足りていない可能性が高い。
そういうときはUpボタン押下時の子ActivityでNavUtils.getParentActivityIntent()
を使って親ActivityへのIntent
を生成したあと、必要なパラメータをセットしてからNavUtils.navigateUpTo()
する。
もちろん親は再生成されてしまうが、子同士の遷移によって親パラメータが変わる可能性もあるので仕方ない。
こういう場合は無理やり再生成を避けるよりはupintent
のパラメータで復元できるようにしたほうが健全な気がする。
他のアプリから遷移した子ActivityでUpが動作しない
他のアプリのタスク上に子Activityがある場合、NavUtils.navigateUpFromSameTask()
では親Activityに遷移しない。
他のアプリから呼ばれる可能性のあるActivityでは以下のように実装する必要がある。
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: Intent upIntent = NavUtils.getParentActivityIntent(this); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // 新しくタスクを生成する必要がある TaskStackBuilder.create(this) .addNextIntentWithParentStack(upIntent) .startActivities(); finish(); } else { NavUtils.navigateUpTo(this, upIntent); } return true; } return super.onOptionsItemSelected(item); }
まとめ
大抵、実装よりもAndroidではUpとBackが別物ということから説明しなければならないのが面倒。
別物だけど単純に階層掘るだけのアプリならActivity#finish()
呼べばそれで済むような気がしなくもない。
もうちょっと楽になってほしい。
ところで、Fragment
遷移の場合のUpってどうするんだろう。