13
6

More than 5 years have passed since last update.

こんにちは。

かねてより気になっていた「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個っきりのアプリです。

activity_main.xml
<?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を追加していきます。

MainActivity.java
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より先に追加しました。この順番も、ポイントですよ。

device-2017-12-07-153708.png

色見の強い画面にしてしまったので、凝視し続けないでください。残像錯視のような面白みはないですので。

fragment_main.xml
<?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>
MainFragment.java
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フォルダに、メニューリソースを置いておきます。

main_menu.xml
<?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>

menu_oneという画像は、この画像です。menu_one.png

ポイントは、

  • onCreateOptionsMenuとonOptionsItemSelectedメソッドを実装している。
  • なので、setHasOptionsMenu(true);しておかなければならない。
  • ここでもonOptionsItemSelectedメソッドの戻り値はfalseにしています。あとでtrueとかで試してみましょう。

サブの方のFragment

「右の方」とか「青の方」です。

aaa
<?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>
SubFragment.java
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を出す位置に注目してください。CENTERLEFTRIGHTと三者三様です。

sub_menu.xml
<?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>

こちらのメニューアイテムのアイコンはこれです。menu_two.png

ActionBar上の右にメニューアイコンが2つ仲良く並ぶことになります。

動作確認

さあ、メニューアイテムをクリックしてみましょう(どちらでもいいです)!なお、3つのonOptionsItemSelectedメソッドはすべてfalseを返しています。

クリックすると...

device-2017-12-07-153739.png

まず、CENTERにMainActivityが出すToastが出たあと...

device-2017-12-07-153750.png

入れ替わりにMainFragmentがLEFTに出すToastが出たあと...

device-2017-12-07-153807.png

最後にお出ましは、SubFragmentがRIGHTにToastを出します。

ぜひ、3か所のreturn文をtrueにしたりfalseにしたりして試してみてください。

onOptionsItemSelectedがfalseをリターンと云うは、伝播する事と見つけたり

葉隠聞書はがくれききがきの一節「武士道と云うは死ぬ事と見付けたり」をもじってカッコつけた気分に浸っています。

閑話休題。

表示されたアプリの画面とは裏腹に、1個のActivityと2個のFragmentの重なり具合は、こういうことだったんですね!画面の見た目にだまされてうっかりActivityが最下層に位置していると勘違いさせられましたよ。

denpa.png

で、onOptionsItemSelectedメソッドでfalseをリターンする、ということはすなわち、consume(消費)せずにproceed(続行)するとな!

まるで、JavaScriptのaddEventListenerにおける「捕捉フェーズ」と似てますね。

今回はMainActivityにて、2つのFragmentをFragmentTransactionを用いて追加(add)したわけですが、その追加順に注意してください。MainFragmentのonOptionsItemSelectedでtrueをリターンしてしまうと、どーしたってSubFragmentには通知が届きません。つくづく画面の見た目(2つのFragmentが並列に並んでいる)に騙されないようにしないといけませんね。

ここから学んだこと

テキトーに「falseをリターンしとけばいい」とするのではなく、 メニュークリック時の処理の責務を明確 にして、 無駄な伝播はオーバーヘッドのもとになるだけ というこを、肝に銘じて今後の武士道を歩む所存に候。

以上です。

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6