最終更新日:190202原本2016-05-18 

Android はじめてのFragment イベント編

この記事はとある勉強会で身内のために作成したもので、こちらの記事の続編にあたります。こちらの記事も、まだFragmentに慣れていない方を対象者として作成させいてただいたものになりますのでご容赦ください。
今回は、Fragmentの実際の活用方法について見ていきましょう。

レイアウトの作成

まずは、Fragmentのレイアウトを作成していきましょう。

ActivityからFragmentへのイベント

ActivityからFragmentへのイベントは、Fragment内のpublicなメソッドを呼ぶことで行います。
そのため、重要になるのは、ActivityからどのようにしてFragmentを取得するかということです。
Fragmentを取得するには、現在FragmentがattachしているContext(ActivityやAppicationなど)からFragmentManagerを取得して、findByTagやfindByIdを使用して取得します。

まずは、Fragmentに呼び出すためのpublicなメソッドを作成しましょう。

MainFragment.java
public void callFromOut() {
    Log.d("MainFragment", "callFromOut this method");
}

次に、Activityから以下のようにして、containerというIDのViewに、"MainFragment"というタグ名で追加したMainFragmentクラスの呼び出しについて考えてみましょう。

MainActivity.java
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.container, new MainFragment(), "MainFragment");

まずは、IDを使用して呼び出し方は以下のようになります。Fragmentでは、addする際に指定したViewのIDがIDになります。
もし、レイアウトからFragmentを追加した場合は内に指定したIDがこれにあたります。

MainActivity.java
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (fragment != null && fragment instanceof MainFragment) {
    ((MainFragment) fragment).callFromOut();
}

次に、タグを使用した呼び出し方は以下のようになります。Fragmentでaddする際に指定したタグを使用してFragmentを呼び出します。

MainActivity.java
Fragment fragment = getSupportFragmentManager().findFragmentByTag("MainFragment");
if (fragment != null && fragment instanceof MainFragment) {
    ((MainFragment) fragment).callFromOut();
}

※注意

Fragmentをaddする際に、supportFragmentManagerを使用した場合、取得の際もsupportFragmentManagerを使用するようにします。supportFragmetManagerは、v4のsupportライブラリを使用したフラグメントに対してのみ使用できますが、取得する際にはどちらも呼び出せるため、混乱しがちです。

FragmentからActivityへのイベント

次に、Fragmentで発生したイベントをActivityで受け取る場合について考えてみましょう。
イベントのやり取りの仕方はActivityとViewとのイベントのやり取りの仕方と同様です。
Viewからイベントを受け取る際は、listenerというものを使用するのでした。例えば、Buttonであれば以下のように、setOnClickListenerというButtonが押された時に呼び出されるイベントを受け取るためのリスナーが用意されています。

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);

Fragmentにはデフォルトでリスナーは用意されていません。これからリスナーを用意してみましょう。
Fragmentのクラス内に内部Interfaceとして作成します。

リスナーの用意

まず、リスナーはJavaのInterface(インターフェース)という仕組みを利用します。Interfaceは、メソッドの構成だけを示した設計書のようなものです。 クラスの継承に非常によく似ていますが、継承とは違い。複数のIntefaceを持つことができます。(直接ここでは関係ない)
では、Fragment内にInterfaceを用意します。
今回はボタンが押された時にだけ呼ばれるリスナーを作りたいので、onClickButtonというメソッドを一つ持つだけのInterfaceを作成します。

MainFragment.java
public interface MyListener {
    public void onClickButton();
}

リスナーの登録

先ほど用意したリスナーを使用してFragmentからActivityへコールバックを返せるようにFragmentにActivityを登録します。
FragmentにActivityを安全に登録するには、FragmentのonAttachメソッドとonDettachメソッドを使用します。

MainFragment.java
public class MainFragment extends Fragment {
    // 変数を用意する
    private MyListener mListener;

    // FragmentがActivityに追加されたら呼ばれるメソッド
    @Override
    onAttach(Context context) {
        // APILevel23からは引数がActivity->Contextになっているので注意する

        // contextクラスがMyListenerを実装しているかをチェックする
        if (context instanceof MyListener) {
            // リスナーをここでセットするようにします
            mListener = (MyListener) context;
        }
    }

    // FragmentがActivityから離れたら呼ばれるメソッド
    @Override
    public void onDetach() {
        super.onDetach();
        // 画面からFragmentが離れたあとに処理が呼ばれることを避けるためにNullで初期化しておく
        mListener = null;
    }
}

また、Activity側にもMyListenerを実装しないとonAttachないでif文の処理が実行されないためActivity側にMyListenerを実装していきます。Activity側で必要な処理は以下のコードだけです。

MainActivity.java
// classにMyListenerをimplementsします。
public class MainActivity extends AppCompatActivity implements MainFragment.MyListener {

    // interface内のメソッドを実装します。
    @Override
    public void onClickButton( ) {
        Toast.makeText(this, "MainFragmentからクリックされました!", Toast.LENGTH_SHORT).show();
    }

}

FragmentからListenerを呼び出す

最後に、Fragmentでボタンが押された際にリスナーのメソッドを呼び出します。
Dettach時にリスナーがnullになるのでNullチェックを行います。

MainFragment.java
public class MainFragment extends Fragment {

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

        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) {
                    mListener.onClickButton();
                }
            }
        });
    }

}

FragmentからFragmentへのイベント

次に、FragmentからFragmentへとイベントを発行することについて考えてみましょう。
FragmentからFragmentへの通知には以下のような方法があります。

  1. getFragmentById()やgetFragmentByTag()メソッドを使って通知先のFragmentのインスタンスを取得して、そのメソッドを呼ぶ
  2. getTargetFragment()メソッドを使って、ターゲットとして指定されているFragmentのインスタンスを取得して、そのメソッドを呼ぶ
  3. getTargetFragment()とgetTargetRequestCode()メソッドを使って、ターゲットとして指定されているFragmentのonActivityResult()メソッドを呼ぶ
  4. getChildFragmentManager()とgetParentFragment()メソッドを使って、親のFragmentのメソッドを呼ぶ方法

それぞれの方法ごとに若干用途が異なりますが、基本的にはどれもFragment間のイベントの受け渡しとして使用することができます。

1.Fragmentのインスタンスから直接呼ぶ方法

Fragmentの特定のメソッドを呼ぶ場合、呼び出し元はそのメソッドを知っている必要があるとう欠点があります。具体的なコードはActivityからFragmentを取得するものと同様なのではぶきます。
FragmentManagerをどのインタンスから作成するかについてだけ気をつけてください。

2.TargetFragmentからFragmentを取得して行う方法

こちらもイベントを飛ばす先のFragmentのインタンスの取得方法が変わっただけで、こちらも1と同様です。

MainActivity.java
int REQUEST_CODE = 100;

MainFragment fragmentMain = new MainFragment();
SubFragment fargmentSub = new SubFragment();

fragmentSub.setTargetFragment(fragmentMain, REQUEST_CODE);

// 通常通りfragmentMainとfragmentSubをaddする

setTargetFragmentされた方から、getTargetFragmentを使用してイベント先のFragmentを取得します。

SubFragment.java
public class SubFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                   // MainFragmentの任意のメソッドの呼び出し
                    ((MainFragment) getTargetFragment()).call(); 
            }
        });
    }
    ...
}

3.getTargetRequestCode()メソッドを使用した方法

getTargetRequestCodeを使用した方法は、ActivityのstartActivityForResultと似ています。

まず、先ほどと同様にfragmentSubにTargetFragmentを指定します。
次に、MainFragmentに以下のようにonActivityResultメソッドを実装します。

MainFragment.java
public class MainFragment extends Fragment {
    ...
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            if(data != null) {
                // dataを使って値を受け取ることもできる
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    ...
}

4.getChildFragmentManager()とgetParentFragment()を使用した方法

getChildFragmentManagerとgetParentFragmentを使用した方法は、Fragmentないから別のFragmentを呼ぶ際に特に使用できます。DialogFragmentなどで多く使用します。

MainFragment.java
public class MainFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        SubFragment subFragment = new SubFragment();
        getChildFragmentManager().beginTransaction().add(R.id.container, subFragment).commit();
    }
    ...
}

SubFragmentからMainFragmentへは、以下のようにしてイベントを返します。

SubFragment.java
public class SubFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                   // MainFragmentの任意のメソッドの呼び出し
                    ((MainFragment) getParentFragment()).call(); 
            }
        });
    }
    ...
}

以上がFragmentを使用したイベントの受け渡しでした。
ライブラリを使用することでイベントの受け渡しを楽にすることもできますが、まずは基本のイベントの受け渡し方とFragmentの注意点をしっかりマスターしましょう。

DialogFragmentを使う

Fragmentのイベントの扱いについて理解したところで、DialogFragmentの使い方について見ていきます。