こんにちは。
かねてより気になっていた「onOptionsItemSelectedの戻り値としてのtrue/falseの意味」を考察してみました。
APIドキュメントがのたまうには
Activity#onOptionsItemSelectedではこう書かれています。
boolean Return false to allow normal menu processing to proceed, true to consume it here.
和訳すれば、メニューをクリックしたことで「処理を続行するならfalseを返せ、費やすならtrueを返せ」といったかんじでしょうか。
実はこのメソッド、Fragment#onOptionsItemSelectedにもあるんです。
boolean Return false to allow normal menu processing to proceed, true to consume it here.
説明文は、一字一句同じですね。
画面を、Activityだけで実現しているのであれば、当件はtrueでもfalseでもどちらでもかまわないのでしょうが、Fragmentも使って画面とメニューを実現している 場合には注意しなければならないポイントとなります。
サンプルアプリ
Activity1個とFragment2個も使って画面とメニューを実現するアプリを使って検証してみます。
Activityとそのレイアウトリソースファイル
Activityは1個っきりのアプリです。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:orientation="horizontal">
</LinearLayout>
背景色は、真っ赤です。中身は空っぽ。プログラムの方で2つのFragmentを追加していきます。
package jp.co.casareal.onoptionsitemselected;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Transactionを利用してFragmentを追加
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.container, new MainFragment());
transaction.add(R.id.container, new SubFragment());
transaction.commit();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast toast = Toast.makeText(getApplicationContext(), "MainActivity", Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
return false;
}
}
とりあえず、onOptionsItemSelectedメソッドの戻り値はfalseにしています。あとでtrueとかで試してみましょう。
メインの方のFragment
「メインの方」とか言いましたけど、以下の画像の「左の方」とか「緑の方」と言ってもいいです。このMainFragmentは、SubFragmentより先に追加しました。この順番も、ポイントですよ。
色見の強い画面にしてしまったので、凝視し続けないでください。残像錯視のような面白みはないですので。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:layout_weight="1"
android:background="#00FF00"
android:padding="20dp">
<TextView
android:id="@+id/text_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="メイン"
android:textColor="@android:color/black"
android:textSize="50sp" />
</LinearLayout>
package jp.co.casareal.onoptionsitemselected;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
public class MainFragment extends Fragment {
private TextView mTextView;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast toast=Toast.makeText(getContext(), "MainFragment", Toast.LENGTH_SHORT);
toast.setGravity(Gravity.LEFT, 0, 0);
toast.show();
return false;
}
}
res/menuフォルダに、メニューリソースを置いておきます。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_main"
android:icon="@drawable/menu_one"
android:title="Mainメニュー"
app:showAsAction="always" />
</menu>
ポイントは、
- onCreateOptionsMenuとonOptionsItemSelectedメソッドを実装している。
- なので、
setHasOptionsMenu(true);
しておかなければならない。 - ここでもonOptionsItemSelectedメソッドの戻り値はfalseにしています。あとでtrueとかで試してみましょう。
サブの方のFragment
「右の方」とか「青の方」です。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:layout_weight="1"
android:background="#0000FF"
android:padding="20dp">
<TextView
android:id="@+id/text_sub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="サブ"
android:textColor="@android:color/white"
android:textSize="50sp" />
</LinearLayout>
package jp.co.casareal.onoptionsitemselected;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
public class SubFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.fragment_sub, container, false);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.sub_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast toast = Toast.makeText(getContext(), "SubFragment", Toast.LENGTH_SHORT);
toast.setGravity(Gravity.RIGHT, 0, 0);
toast.show();
return false;
}
}
Toastを出す位置に注目してください。CENTER
、LEFT
、RIGHT
と三者三様です。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_sub"
android:icon="@drawable/menu_two"
android:title="Subメニュー"
app:showAsAction="always" />
</menu>
ActionBar上の右にメニューアイコンが2つ仲良く並ぶことになります。
動作確認
さあ、メニューアイテムをクリックしてみましょう(どちらでもいいです)!なお、3つのonOptionsItemSelectedメソッドはすべてfalseを返しています。
クリックすると...
まず、CENTER
にMainActivityが出すToastが出たあと...
入れ替わりにMainFragmentがLEFT
に出すToastが出たあと...
最後にお出ましは、SubFragmentがRIGHT
にToastを出します。
ぜひ、3か所のreturn文をtrueにしたりfalseにしたりして試してみてください。
onOptionsItemSelectedがfalseをリターンと云うは、伝播する事と見つけたり
葉隠聞書の一節「武士道と云うは死ぬ事と見付けたり」をもじってカッコつけた気分に浸っています。
閑話休題。
表示されたアプリの画面とは裏腹に、1個のActivityと2個のFragmentの重なり具合は、こういうことだったんですね!画面の見た目に騙されてうっかりActivityが最下層に位置していると勘違いさせられましたよ。
で、onOptionsItemSelectedメソッドでfalseをリターンする、ということはすなわち、consume(消費)せずにproceed(続行)するとな!
まるで、JavaScriptのaddEventListenerにおける「捕捉フェーズ」と似てますね。
今回はMainActivityにて、2つのFragmentをFragmentTransactionを用いて追加(add)したわけですが、その追加順に注意してください。MainFragmentのonOptionsItemSelectedでtrueをリターンしてしまうと、どーしたってSubFragmentには通知が届きません。つくづく画面の見た目(2つのFragmentが並列に並んでいる)に騙されないようにしないといけませんね。
ここから学んだこと
テキトーに「falseをリターンしとけばいい」とするのではなく、 メニュークリック時の処理の責務を明確 にして、 無駄な伝播はオーバーヘッドの素になるだけ というこを、肝に銘じて今後の武士道を歩む所存に候。
以上です。