onOptionsItemSelectedの戻り値の使い道

こんにちは。

かねてより気になっていた「onOptionsItemSelectedの戻り値としてのtrue/falseの意味」を考察してみました。

# APIドキュメントがのたまうには

[Activity#onOptionsItemSelected](https://developer.android.com/reference/android/app/Activity.html#onOptionsItemSelected(android.view.MenuItem))ではこう書かれています。

`boolean Return false to allow normal menu processing to proceed, true to consume it here.`

和訳すれば、メニューをクリックしたことで「処理を続行するならfalseを返せ、費やすならtrueを返せ」といったかんじでしょうか。

実はこのメソッド、[Fragment#onOptionsItemSelected](https://developer.android.com/reference/android/app/Fragment.html#onOptionsItemSelected(android.view.MenuItem))にもあるんです。

`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: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を追加していきます。

```java: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](https://qiita-image-store.s3.amazonaws.com/0/100612/aac2f207-48ac-c98e-4cdf-ff9b36e5b561.png)

色見の強い画面にしてしまったので、凝視し続けないでください。[残像錯視](https://www.youtube.com/watch?v=EmXzqVm9PuY)のような面白みはないですので。


```xml: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>
```

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

```xml: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](https://qiita-image-store.s3.amazonaws.com/0/100612/e6483135-ca09-c551-58d1-ebb352b4ead6.png)



ポイントは、

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

## サブの方のFragment

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

```xml: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>
```

```java: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を出す位置に注目してください。`CENTER`、`LEFT`、`RIGHT`と三者三様です。

```xml: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](https://qiita-image-store.s3.amazonaws.com/0/100612/1088e9c5-f6eb-90f8-c1b0-f4028014236d.png)

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

# 動作確認

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

クリックすると...

![device-2017-12-07-153739.png](https://qiita-image-store.s3.amazonaws.com/0/100612/e855f34b-d0de-c304-eb5a-1ec42fb2ebf0.png)

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

![device-2017-12-07-153750.png](https://qiita-image-store.s3.amazonaws.com/0/100612/4f9ce151-7609-f333-b21d-1e29f7b6c7c5.png)

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

![device-2017-12-07-153807.png](https://qiita-image-store.s3.amazonaws.com/0/100612/f9752cea-675b-8fd5-d304-03752c78234c.png)

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

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

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

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

閑話休題。

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

![denpa.png](https://qiita-image-store.s3.amazonaws.com/0/100612/3d7ea933-d861-0139-9f7e-ed334674814d.png)

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

まるで、[JavaScriptのaddEventListener](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)における「捕捉フェーズ」と似てますね。

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

# ここから学んだこと

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

以上です。