[Android]Fragmentの使い方

投稿者:

概要

複数のアクティビティで同じレイアウトを使い回したいことがあると思う。そんなときはそれぞれのアクティビティで同じレイアウトxmlを使うのもひとつの手だ。ただ、xmlには、ボタンがタップされたときの動作、チェックボックスのチェックマークが切り替わったときの動作等、ユーザのアクションに対する応答ロジックが定義されていないため、それらをアクティビティ毎に定義する必要がある。もしロジックに変更があると、すべてのアクティビティのコードを修正しなければならないので非常に手間がかかり、バグの温床になる可能性がある。

そんな面倒を解消するため(かどうかはわからないが)、レイアウトとロジックをセットにして部品化した仕組みがFragmentである。

Fragmentを使って画面をデザインする場合、Activityは大まかな画面レイアウトを定義するだけにとどめて、ボタンやテキストの表示といった細かなViewの処理はFragmentで行うようにする。ActivityとFragmentは親子関係にあるものの別個のものなので、複数のActivityで同じFragmnentを使い回すことができる。

Fragmentの定義のコツ

最低限実装するメソッド

Fragmentクラスを継承したら最低限実装しなければならないメソッドはonCreateView()である。ここでFragmentで表示するViewを生成し、必要であればボタン等にリスナーを登録する。onCreate()もあるけどそこでViewを生成することはできない。

public class MyFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_sample1, container, false);
    }
}

コンストラクタ引数でパラメータを渡してはいけない

「Fragment」と名の付くオブジェクトに共通の約束事と言えるのだが、Fragmentではコンストラクタ引数でパラメータを渡してはいけない。

Androidではメモリ不足や画面方向の変化等でアクティビティが破棄→再作成されるが、Fragmentも同様で、何らかの理由で破棄され、必要になったら再生成される。再生成時に引数なしのコンストラクタが呼ばれるため、引数付きのコンストラクタがあったらエラーが発生してアプリが落ちてしまうのだ。

Fragmentにパラメータを渡したいときは、Bundleに値を格納してsetArgument()でインスタンスに登録する。

MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("param1", 1);
fragment.setArguments(args);

上記のやり方では、毎回4行くらいのコードを書かなければならず面倒だ。 だから、Fragmentを継承したクラスに、インスタンスを返すstaticなメソッドを実装し、それを呼び出すのが良いとされている。

public class MyFragment extends Fragment {
    //インスタンスを作成するstaticメソッド
    public static MyFragment newInstance(int param) {
        MyFragment fragment = new MyFragment();   //インスタンス作成
        Bundle args = new Bundle();
        args.putInt("param1", param); //パラメータをBundleに格納
        fragment.setArguments(args); //インスタンスにBundleを登録
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        int param1 = getArguments().getInt("param1"); //パラメータの取り出し
        
        ~
        ~
        ~
    }

    ~
    ~
    ~

}

こうしておけば……

MyFragment fragment = Myfragment.newInstance(1);

……という感じで一行でFragmentにパラメータを渡しつつインスタンスを作成することができるわけだ。

実装の仕方

静的な実装

アクティビティが破棄されるまでずっと表示されているFragmentはアクティビティのレイアウトxmlに記述するのが手っ取り早く、コードをシンプルにすることができる。

以下はそのサンプルだ。

Fragmentの定義
レイアウト【fragment_sample1.xml】
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#CDCDCD">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:textSize="40sp"
        android:textColor="@android:color/holo_red_dark"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="World"
        android:textSize="40sp"
        android:textColor="@android:color/holo_blue_dark"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>
JAVA【SampleFragment1.java】
public class SampleFragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_sample1, container, false);
    }
}
Activityの定義
レイアウト【activity_main.xml】
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Main Activity" />

    <!-- Fragmentを静的に実装 -->
    <!-- Fragmentはあらかじめ定義しておき、name属性で指定する -->
    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.fragmentsample1.SampleFragment1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
JAVA【MainActivity.java】
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

アプリを実行すると以下のような画面が表示される。

「Hello World」と表示されているグレー背景の部分がFragmentで表示している箇所だ。MainActivityのコードを見ると、Fragmentに関してはXMLファイルに書かれているだけで、JAVAでFragmentのインスタンスを作成するといった処理が書かれていないのがわかるだろう。

動的な実装

ボタンやタブで画面を切り替えるような場合、動的にFragmentを実装する。

大まかな流れを説明すると

  1. アクティビティのレイアウトにFragmentを展開するコンテナ(LinearLayout等)を用意する。
  2. 表示するFragmentのインスタンス生成
  3. FragmentManagerを取得
  4. FragmentTransactionを生成
  5. Fragmentの処理方法を登録(追加・置換・削除・非表示など)
  6. commit()で実行

トランザクションを開始してから処理を記述し、コミットで確定させるやり方は、データベースのトランザクション処理と似ているので、Oracle等のデータベースを扱ったことのある人は馴染みがあるのではないだろうか。

そんなわけでサンプルを以下に示す

Fragmentの定義
レイアウト【fragment_sample1.xml】
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#CDCDCD">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:textSize="40sp"
        android:textColor="@android:color/holo_red_dark"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="World"
        android:textSize="40sp"
        android:textColor="@android:color/holo_blue_dark"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>
JAVA【SampleFragment1.java】
public class SampleFragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_sample1, container, false);
    }
}
レイアウト【fragment_sample2.xml】
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFCDCD">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Happy"
        android:textSize="40sp"
        android:textColor="@android:color/holo_green_dark"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Days"
        android:textSize="40sp"
        android:textColor="@android:color/holo_orange_dark"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>
JAVA【SampleFragment2.java】
public class SampleFragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_sample2, container, false);
    }
}
MainActivityの定義
レイアウト【activity_main.xml】
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- ボタンを配置するコンテナ -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fragment1"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="fragment2"/>

    </LinearLayout>

    <!-- Fragmentを展開するコンテナ -->
    <RelativeLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
JAVA【MainActivity.java】
public class MainActivity extends AppCompatActivity
                            implements View.OnClickListener{
    //メンバ変数
    Button button1;
    Button button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1 = findViewById(R.id.button1);
        button1.setOnClickListener(this);
        button2 = findViewById(R.id.button2);
        button2.setOnClickListener(this);
    }

    //Fragmentを表示する
    //引数:fragmentNumber = Fragmentの番号
    private void displayFragment(int fragmentNumber) {
        Fragment fragment;

        //引数で渡された番号のFragmentのインスタンスを作成
        switch (fragmentNumber) {
            case 1:
                fragment = new SampleFragment1();
                break;
            case 2:
                fragment = new SampleFragment2();
                break;
            default:
                return;
        }
        //FragmentTransactionを生成して処理を開始
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        //レイアウトコンテナにFragmentを展開
        transaction.replace(R.id.fragment_container, fragment);
        //処理実行
        transaction.commit();
    }

    //ボタンがクリックされたときの処理
    @Override
    public void onClick(View v) {
        if (v.equals(button1)) {
            displayFragment(1);
        } else if (v.equals(button2)) {
            displayFragment(2);
        }
    }
}

アプリを実行すると下のような画面が表示される。

「FRAGMENT1」ボタンをクリックするとグレー背景の「Hello World」が表示され、そこから「FRAGMENT2」ボタンをクリックすると、Fragmentが入れ替わり、ピンク背景の「Happy Days」が表示される。逆の順序でボタンをクリックしても同様だ。

FRAGMENT1
FRAGMENT2

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください