最終更新日:190202原本2018-08-24 

Android

Android はじめてのFragment

この記事はとある勉強会で身内のために作成したもので、Fragmentをまだあまり使ったことの無い方が、どうしてFragmentを使うのかと、どこから始めればいいのかをまとめたものになります。

Fragment(フラグメント)とは?

Fragmentとは、簡単にいうと、コンテンツとライフサイクルを持ったビューです。
プログラミングでいうライフサイクルとは、インスタンスが作成されてから、それが捨てられるまでの一連の流れのことをいいます。
Androidでは、例としてActivity(アクティビティ)だとインスタンスが作成される際にonCreateメソッドが呼ばれ、破棄される際にonDestroyメソッドが呼ばれ、他にも画面の状態によって、onResume・onStart・onPause・onStopなどのメソッドが呼ばれます。
このように、ライフサイクルに応じて呼び出されるメソッドを持っている場合にライフサイクルを持っていると表現します。
そして、Fragmentもアクティビティに非常に近いライフサイクルを持っています。

アクティビティなどとの違い

では、実際にアクティビティやカスタムビューとはどう違うのか?

まずは、それぞれの特徴を整理してみましょう。
まず、アクティビティとFragmentとの一番の違いはFragmentは親子関係を持てるという点です。Fragmentは、Fragmentの中に更にFragmentを持つことができます。また、ActivityとFragment別々なものなので、Fragmentは複数の画面で使い回すことができます。
次に、カスタムビューとの違いは、よりActivityにそったライフサイクルイベントを持っているという点です。ビューもライフサイクルを持っていますが、ActivityやFragmentのように細かなイベントは持っていません。

これらの特徴からFragmentの立ち位置を整理すると、以下のようになります。

fragment.001.jpeg

もう一度整理すると、FragmentはActivityのようなライフサイクルを持ちながら、複数のビューなどの制御を行うのに適したパーツという事になります。

Fragmentを取り入れた設計

では、実際にFragmentを設計として組み込んでいきましょう。
まず、Activityの役割はFragmentを持つだけの、全体のデザインのパターンを表現するためだけの箱として考えましょう。
ボタンやテキストの表示といった細かなビューの処理はすべてFragmentがやるようにします。
実際にどのように処理を分けるか考えてみましょう。

例として、今回はDrawerパターンという横からメニューが出てくるデザインのアプリを考えましょう。
皆さん見たことがあると思いますが、PlayStoreやPlayMusicなど多くのアプリの使われているデザインパターンです。
このDrawerパターンのアプリを作る際、一番上のViewはDrawerLayoutというViewになります。その下にコンテンツ、更にその下に横から出てくるメニュー部分のViewを用意することでDrawerパターンを作ることができます。

下の図が実際のアプリの図になります。さて、この中でそれぞれのパーツを色によって分けましたがFragmentはどこに使われるでしょうか?

  • 赤:DrawerMenu
  • 青:メニュー部分も含む全体
  • 緑:コンテンツ

fragment.002.jpeg

正解は、、、
赤のDrawerMenu部分と、緑のコンテンツ部分です。

まず、青のアクティビティ部分はDrawerの開け閉めと、どのコンテンツを表示するかだけを管理しており、表示されている中身については一切気にしなくてよくなります。
次に、赤のDrawerMenuについてです。ここもFragmentにすることでメニューの中身が押された時の処理も含め書くことができるようになります。もし、複数のアクティビティでDrawerのメニューを使いたいということがあった場合同じFragmentを使うことできるようになります。
最後に、緑のContent部分については、DrawerMenuの選択によってここの表示をガラッと変える際にFragmentを差し替えることで表示しているコンテンツを変えることができます。
このように画面内のとあるコンテンツをガラッと変える際にFragmentは向いています。

Fragment実装

では、いよいよFragmentの使い方について見ていきましょう。

プロジェクトの作成

最初に作るActivityにはEmptyActivityを選択します。
01_createproject.png

Fragmentの作成

まずは、レイアウトから作成していきます。

Main画面のFragmentなので、ファイル名は"fragment_main.xml"にします。
スクリーンショット 2015-10-25 15.38.03.png
今回は簡単にレイアウトファイル内には下のようなTextViewとButtonを置きます。
スクリーンショット 2015-10-25 15.40.22.png

レイアウトが終わったら、コードの方を書いていきます。
"MainFragment.java"という名前でJavaファイルを作成しましょう。
スクリーンショット 2015-10-25 15.40.54.png

ファイルを作成したらコードを書いていきます。
まずは、Fragmentクラスを継承します。

  • android.support.v4.app.Fragment;
  • android.app.Fragment;

対応したいAPIレベルが16より低い場合はsupport-packageを使用します。
Fragmentでは、Viewを作る際にonCreateViewが呼ばれ、そこでFragmentのViewを返します。
onCreateメソッドも存在しますがこちらではViewを作ることはできません。

MainFragment.java
import android.os.Bundle;
// 下位のバージョンにも対応させる場合はsupport-v4パッケージを使用します
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

// Fragmentクラスを継承します
public class MainFragment extends Fragment {

    // Fragmentで表示するViewを作成するメソッド
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        // 先ほどのレイアウトをここでViewとして作成します
        return inflater.inflate(R.layout.fragment_main, container, false);
    }
}

次にFragment内のViewのアクションを記述していきます。
onCreateViewないでアクションの設定をしてもいいですが、onViewCreatedがViewの生成後に呼ばれるメソッドのため、今回はこちらで処理を行ってみましょう。

MainFragment.java
public class MainFragment extends Fragment {

    private TextView mTextView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        /* 略 */
    }

    // Viewが生成し終わった時に呼ばれるメソッド
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // TextViewをひも付けます
        mTextView = (TextView) view.findViewById(R.id.textView);
        // Buttonのクリックした時の処理を書きます
        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextView.setText(mTextView.getText() + "!");
            }
        });
    }
}

Fragmentを使う

Fragmentを利用する方法は2種類あります。

1つ目:レイアウトから追加

activity_main.xmlファイルを開き、レイアウトエディタの左下からを選択します。
普通のレイアウトのincludeと同様にFragmentを追加することができます。

スクリーンショット 2015-10-25 15.51.40.png

このような形で表示では見えませんが、問題ありません。
スクリーンショット 2015-10-25 15.52.00.png

ここまで出来たら一度動かしてみましょう。

2つ目:コードから動的に追加

activity_main.xmlファイルを開き、先ほどのFragmentを一度消して、LinearLayout(vertical)を追加しましょう。
スクリーンショット 2015-10-25 18.48.14.png

次に、MainActivity.javaを開き、onCreateメソッド内にコードを書いていきます。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // コードからFragmentを追加

        // Fragmentを作成します
        MainFragment fragment = new MainFragment();
        // Fragmentの追加や削除といった変更を行う際は、Transactionを利用します
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        // 新しく追加を行うのでaddを使用します
        // 他にも、よく使う操作で、replace removeといったメソッドがあります
        // メソッドの1つ目の引数は対象のViewGroupのID、2つ目の引数は追加するfragment
        transaction.add(R.id.container, fragment);
        // 最後にcommitを使用することで変更を反映します
        transaction.commit();
    }
}

ここまで出来たら一度動かしてみましょう。
1つ目の方法と変わらず表示されていれば大丈夫です。

device-2015-10-25-155830.png

もう一つ同じFragmentを画面に追加してみましょう。

transaction.add(R.id.container, fragment);

のあとに、同じくtransaction.addをもう一度呼びましょう。
LinearLayoutでビューを追加するのと同様に2つ画面内に表示されます。

このようにFragmentを使うと画面丸々から、画面の一部までアクティビティの処理を分けて記述することができます。

Fragmentへ値を渡す

Fragmentの中で少し処理を分けたいという時のために、Fragmentへ値をセットする方法について見ていきます。
まずは、先ほどのMainFragmentに値を受け取るための処理を書いていきます。
値の受け渡しには、BundleというKeyPairで値を持つ事のできるクラスを使用します。
まずは、Bundleのインタンスを作成し、値をKeyPairの形でputして追加していきます。
そして、FragmentにsetArgumentという形でセットすることでフラグメントがBundleの値を取得できるようになります。

今回は、ActivityとFragmentを完全に分けるために、Fragment内にcreateInstanceメソッドを作成して、ActivityはそのメソッドからFragmentを作成するようにする。

MainFragment.java
public class MainFragment extends Fragment {
    // このクラス内でだけ参照する値のため、BundleのKEYの値をprivateにする
    private final static String KEY_NAME = "key_name";
    private final static String KEY_BACKGROUND = "key_background_color";

    // このメソッドからFragmentを作成することを強制する
    @CheckResult
    public static MainFragment createInstance(String name, @ColorInt int color) {
        // Fragmentを作成して返すメソッド
        // createInstanceメソッドを使用することで、そのクラスを作成する際にどのような値が必要になるか制約を設けることができる
        MainFragment fragment = new MainFragment();
        // Fragmentに渡す値はBundleという型でやり取りする
        Bundle args = new Bundle();
        // Key/Pairの形で値をセットする
        args.putString(KEY_NAME, name);
        args.putInt(KEY_BACKGROUND, color);
        // Fragmentに値をセットする
        fragment.setArguments(args);
        return fragment;
    }

    /** 省略 **/

次に、onCreateメソッドを作成し、その中で値を受け取る。
onViewCreatedなどで受け取ってもいいが、値のセットなど変数のロジックに関する部分は"View"とついていないメソッドの中で行うことでロジックの部分と表示の部分を分ける。

MainFragment.java
public class MainFragment extends Fragment {

    /** 省略 **/

    // 値をonCreateで受け取るため、新規で変数を作成する
    // 値がセットされなかった時のために初期値をセットする
    private String mName = "";
    private @ColorInt int mBackgroundColor = Color.TRANSPARENT;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Bundleの値を受け取る際はonCreateメソッド内で行う
        Bundle args = getArguments();
        // Bundleがセットされていなかった時はNullなのでNullチェックをする
        if (args != null) {
            // String型でNameの値を受け取る
            mName = args.getString(KEY_NAME);
            // int型で背景色を受け取る
            mBackgroundColor = args.getInt(KEY_BACKGROUND, Color.TRANSPARENT);
        }
    }

    /** 省略 **/

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

        // ラストに追加
        // 背景色をセットする
        view.setBackgroundColor(mBackgroundColor);
        // onCreateで受け取った値をセットする
        mTextView.setText(mName);
    }
}

最後に、ActivityからのFragmentの呼び出しを修正する

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        /** 省略 **/
        transaction.add(R.id.container, MainFragment.createInstance("hoge", Color.RED));
        transaction.add(R.id.container, MainFragment.createInstance("fuga", Color.BLUE));
        /** 省略 **/
    }
}

これで一度実行してみましょう。
以下のようになれば成功です!

device-2015-11-01-160141.png

以上が、基本的なFragmentの使い方でした。

Fragmentでのイベント処理についてはこちらを御覧ください。

おまけ:Activityに対し、1つのFragmentをセットする場合

Activityにたいして、Fragmentをセットする場合には、画面の再生成時のことを考える必要があります。
そのため、ポイントとして以下の2つを絶対に抑えてコードを書きましょう。

  • savedInstanceStateがnull出ない場合は、画面が再利用されている
  • Fragmentは、Activityによって自動で再利用される
  • addが2回行われたりして、無駄にFragmentが積まれないようにする
MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 画面がはじめて作成された時ににだけ、Fragmentを追加する
        if (savedInstanceState == null) {
            // Fragmentを作成します
            MainFragment fragment = new MainFragment();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            // 1つだけなので、念のためreplaceを使用します
            transaction.replace(R.id.container, fragment);
            // 最後にcommitします
            transaction.commit();
        }
    }
}