ActivityとFragment、両方向の操作

前回実装したタブ（<a href="https://qiita.com/MotoyaAsahina/items/a0282483831b5993da02">TabLayoutとViewPagerを利用したタブの実装 - Qiita</a>）をさらに拡張していきます。今回はActivityからViewPager内のFragmentのメソッドを呼ぶ方法と、ViewPager内のFragmentからActivityのメソッドを呼ぶ方法を紹介します。
#ActivityからFragmentを操作する
##使用例
ActivityのToolbarに作成したメニューがクリックされた時に、Fragment内のEditTextに入力された文字列を保存して、Activityを終了する。
##流れ
１.FragmentにToolbarのメニューがクリックする時に呼ばれるpublicなメソッドを作成する。
２.Toolbarのメニューがクリックされた時、ViewPagerでどのFragmentが表示されているかを取得する。
３.取得されたFragmentによって、１で作成したメソッドを呼ぶ。
４.Activityを終了する。
##コード
使用例に沿ったものを作っていきます。今回、文字列の保存はSharedPreferencesで行います。

とりあえずこんな感じのLayoutにしました。EditTextを１つ配置しただけです。

```xml:layout/fragment_main2.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context="...Main2Fragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="fragment2" />

    <EditText
        android:id="@+id/editText2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="text" />
</LinearLayout>
```
Fragmentを変更します。closeAndSave()メソッドをToolbarのメニューがクリックされた時に呼びます。
onViewCreated()メソッドで保存してある文字列をEditTextにセットするようにしました。これで、実際にFragmentのメソッドがActivityから呼ばれているかが確認できます。

```java:Main2Fragment.java
package ...;

import ...;

public class Main2Fragment extends Fragment {

    private EditText editText;
    private SharedPreferences preferences;

    ...

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        editText = view.findViewById(R.id.editText2);

        preferences = getContext().getSharedPreferences("data", Context.MODE_PRIVATE);
        editText.setText(preferences.getString("text", ""));
    }

    public void closeAndSave() {
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("text", editText.getText().toString());
        editor.apply();
    }
}
```
次にAdapterを変更します。これはActivityが、ViewPagerがどのFragmentを表示しているかをAdapterから取得する必要があるためです。
新たに、setPrimaryItem()というオーバーライドメソッドを表記しました。このメソッドはViewPagerのページが変え始められた時と、表示するページが確定した時に呼ばれます。要するにViewPagerをスワイプして右のページを表示した時、スワイプし始めた瞬間に１回呼ばれ、指を離して右のページの表示が完了した時にもう１回呼ばれます（logで確認してみて下さい）。ここで、positionとfragmentにそれぞれ引数を代入していますが、今回ViewPagerのページ切り替え中には特に何もしないので、１回のページ切り替えで２回呼ばれるメソッドのうち、基本的にページが確定した時に代入された値のみを使うことになります。
引数のObjectはViewPagerで表示されているもの、すなわちこの場合はFragmentなので型を変えて代入できます。

```java:OriginalFragmentPagerAdapter.java
package ...;

import ...;

public class OriginalFragmentPagerAdapter extends FragmentPagerAdapter {

    private int position;
    private Fragment fragment;

    ...

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);

        this.position = position;
        fragment = (Fragment) object;
    }

    public int getPosition() {
        return position;
    }

    public Fragment getFragment() {
        return fragment;
    }
}
```
最後にActivityです。Toolbarにメニューを表示するところから書きます。
メニューがクリックされた時にclose()メソッドが呼ばれ流ようにしました。まずpositionをAdapterから取得しています。今、Toolbarのメニューをクリックして呼びたいメソッドを書いたのはMain2Fragmentだけなので、Main2Fragmentが表示されている、すなわちpositionが１の時のみMain2FragmentのcloseAndSave()メソッドが呼ばれるようにしています。よって、現段階ではMain1Fragmentが表示されているときにメニューをクリックしてもActivityが終了するだけです。
また、onMenuItemClick()メソッドの最後のfinish()というコードはActivityを終了するコード（メソッド）です。よって、close()メソッド内のFragmentから呼ばれたメソッドが終了したタイミングでActivityが終了します。

```java:MainActivity.java
package ...;

import ...;

public class MainActivity extends AppCompatActivity {
    
    private OriginalFragmentPagerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        Toolbar toolbar = findViewById(R.id.toolbar);
        toolbar.inflateMenu(R.menu.menu_main);
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.menu_save:
                        close();
                        break;
                }
                finish();
                return false;
            }
        });
    }

    private void close() {
        int position = adapter.getPosition();
        switch (position) {
            case 0:
                break;
            case 1:
                ((Main2Fragment) adapter.getFragment()).closeAndSave();
                break;
        }
    }
}
```

```xml:menu/menu_main.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_save"
        android:icon="@drawable/ic_done_white_24dp"
        android:title="Save"
        app:showAsAction="always" />
</menu>
```
#FragmentからActivityを操作する
##使用例
あまり、使用する機会があるのかわかりませんがとりあえず。。。
EditTextで入力された文字列をリアルタイムで（入力と同時に）Activity上のToolbarのタイトルに反映させる。
##流れ
１.Activityに、引数で受け取った文字列をToolbarのタイトルにセットするパブリックなメソッドを作る。
２.EditTextに入力を検知するリスナーをセットして、文字列が変更されるたびに１で作成したメソッドをよび、文字列を渡す。
##コード
今度はMain1Fragmentに実装します。
Layoutは前回のまま使用できるので、まずはActivityにToolbarにタイトルをセットするメソッドを作ります。

```java:MainActivity.java
package ...;

import ...;

public class MainActivity extends AppCompatActivity {

    ...

    private Toolbar toolbar;

    private void setToolbarTitl(String title) {
        toolbar.setTitle(title);
    }
}
```
ActivityからFragmentを操作する時と異なるのは、今回は操作対象の親であるActivityが１つしかないので判別する必要がないということです。よって次にFragmentを編集するだけで、これは実装できます。

```java:Main1Fragment.java
package ...;

import ...;

public class Main1Fragment extends Fragment {

    ...

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {

        ...

        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                ((MainActivity) getContext()).setToolbarTitle(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }
}
```
#最後に
このようにActivityとFragmentは両方向の操作が可能です。