従来は、レイアウトを解像度ごとや種別ごとに作成することで対応していましたが、大きく異なる場合にはそれぞれに適した操作性を提供する必要があり、プログラム内で処理を切り分ける必要がありました。このため、プログラムの見通しが悪くなるといった問題が発生していました。
これらの問題を解決する方法として、Android 3.0ではFragmentと呼ばれるクラス群が追加されました。
これまではActivityとしてしかカプセル化できなかったViewを、Fragmentという形でレイアウトに埋め込むことができるようになったため、様々なディスプレイタイプに合わせたレイアウトを柔軟に作成することができるようになりました。
なお、FragmentはAndroid Compatibility packageをリンクさせることで、Android 1.6以降のOSでも利用可能です。
Fragmentの使用例として、下図のようにタブレットのような大きな画面でマルチペインのアプリケーションを作成することができます。図の赤枠がFragmentです。
Fragmentを使用する場合、Activityは画面全体を表していて、FragmentはActivity状に配置されるパーツ(破片)というイメージです。Fragmentは独自のライフサイクルを持っていて、Fragment自体が入力イベントを受け取り、Activityの実行中にFragmentを追加したり取り除いたりすることができます。
またFragmentはActivity上に配置されているため、Activityのライフサイクルの影響も受けます。例えば、Activityが一時停止されるとFragmentも同様に一時停止状態となり、Activityが破棄されるとFragmentも破棄されます。
FragmentはFragmentManager(1つのActivity毎に1つ存在する)によって管理されています。
したがって、新たにFragmentを追加する場合は、FragmentManagerにFragmentオブジェクトを追加するという方法で行います。
Activityと同じく、標準のFragmentクラスだけでなく、特定の用途に合わせて拡張されたFragmentクラスが提供されています。よく使われるFragmentのサブクラスを以下の表に示します。
クラス名 | 概要 |
---|---|
DialogFragment | ダイアログを提供するために拡張されたクラス |
ListFragment | 一覧(ListView)の表示等を提供するために拡張されたクラス |
PreferenceFragment | アプリケーションの設定情報を容易に扱えるように拡張されたクラス |
WebViewFragment | ブラウザ(WebView)の表示を提供するために拡張されたクラス |
特定のタイミングで対応するメソッドが呼び出される仕組みとなっています。
各メソッドの概要は下表の通りです。
メソッド名 | 概要 |
---|---|
onAttach() | Activityに関連付けされた際に一度だけ呼び出される |
onCreate() | Fragmentの初期化処理を行う |
onCreateView() | FragmentのView階層を生成し戻り値として返す |
onActivityCreated() | 親となるActivityの「onCreate」の終了を知らせる |
onStart() | Activityの「onStart()」にもとづき開始される |
onResume() | Activityの「onResume()」にもとづき開始される |
onPause() | Activityが「onPause()」になった場合や、Fragmentが更新されて、操作を受け付けなくなった場合に呼び出される |
onStop() | フォアグラウンドでなくなった場合に呼び出される |
onDestroyView() | Fragmentの内部のViewリソースの整理を行う |
onDestroy() | Fragmentが破棄されるとき、最後に呼び出される |
onDetach() | Activityの関連付けから外されたときに呼び出される |
先に説明しましたが、上記FragmentのライフサイクルはActivityのライフサイクルに依存します。
Activityの状態に対するFragmentで呼び出されるメソッドは下表の通りです。
例えば、ActivityのonPause()メソッドが呼ばれると、そのActivityに組み込まれているそれぞれのFragmentのonPause()メソッドが呼ばれるということになります。
また、Activityの場合と同様に、すべてのメソッドをオーバーライドする必要はありません。
必要に応じて各々のメソッドをオーバーライドします。
Activityの状態 | Fragmentで呼び出されるメソッド |
---|---|
Created | onAttach() |
onCreate() | |
onCreateView() | |
onActivityCreated() | |
Started | onStart() |
Resumed | onResume() |
Paused | onPause() |
Stopped | onStop() |
Destroyed | onDestroyView() |
onDestroy() | |
onDetach() |
- FrameLayoutをレイアウトXMLファイルに定義
- FragmentのレイアウトXMLファイルを作成
- Fragmentクラスを作成
- Fragmentクラスを継承した独自クラスをインスタンス化し、FrameLayoutに追加
実際にFragmentをActivity上に配置する方法の1つは、FrameLayoutで配置位置等のプロパティをあらかじめレイアウトXMLファイルに定義しておくという方法です。
res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ... > <FrameLayout android:id="@+id/frameLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" ... /> </RelativeLayout>
Fragmentのレイアウトとして使用するレイアウトXMLファイル(fragment_1.xml)を作成します。
res/layout/fragment_1.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ... > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" /> </RelativeLayout>
次にFragmentクラスを継承し、独自クラスを作成します。
src/com.example.fragmentsample/MainActivity.java
public class MainActivity extends Activity { //...省略... private static class MyFragment extends Fragment{ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_1, container, false); TextView text = (TextView)view.findViewById(R.id.textView1); text.setText("Hello Fragment !!"); return view; } } }
作成したFragmentを継承する独自クラスとレイアウトXMLファイルを関連付けるには、onCreateView()メソッドをオーバーライドし、このメソッドの戻り値に関連付けされたViewオブジェクトを設定します。
onCreateView()メソッドの引数にLayoutInflaterオブジェクトが提供されていて、inflate()メソッドでレイアウトXMLファイルと関連付けを行うことができます。
第1引数に紐付けを行うレイアウトXMLファイルのリソースIDを指定します。
第3引数のattachToRootは、trueにするかfalseにするかで戻り値となるルートビュー(View)が変わります。
trueとした場合は第2引数のViewGroupが、falseとした場合は第1引数のリソースIDで指定したXMLファイルのルートビューとなります。
最後に、MainActivityクラス内でMyFragmentクラスをインスタンス化し、FragmentManager経由で追加します。
src/com.example.fragmentsample/MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(R.id.frameLayout1, new MyFragment()); ft.commit(); }
6行目ではFragmentManager.beginTransaction()を用いて、FragmentTransactionオブジェクトを取得しています。
7行目では6行目で取得したFragmentTransactionオブジェクトのadd()を使い、追加するFragmentオブジェクトとそれを挿入するViewGroupを指定することでフラグメントを追加しています。add()メソッドに渡される第1引数には、フラグメントが配置されるViewGroupのリソースIDを指定し、第2引数には追加するFragmentオブジェクトを指定します。最後にFragmentTransactionクラスのcommit()メソッドを呼び出すことで、Activityに配置する処理を完了させています。
以上を作成後、実行すると下図のようになります。
FragmentManager fm = getFragmentManager(); NoUIFragment workFragment = new NoUIFragment(); fm.beginTransaction().add(workFragment, "work").commit();
add()メソッドはオーバーロードされており、第2引数に文字列を指定するとその文字列がタグとなります。
Button button = getActivity().findViewById(R.id.button);
同様にFragmentManagerからfindViewById()を用いてFragmentオブジェクトを取得することで、Fragmentにあるメソッドを呼び出すことも可能です。
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); fragment.clickButton1();
ActivityとFragmentで発生したイベントの共有を行う必要があった場合、Fragmentの内部でコールバックメソッド用のインタフェースを定義し、Activityにこのインタフェースを実装させる方法が推奨されています。
Activityがインタフェースを介して、あるイベントを受け取った際に、Activityは必要に応じてメソッドを呼び出すことで、Activity上にある他のFragmentと情報を共有することができるようになります。
例えば、一方がURLのリスト(LeftFragment)で、もう片方がWebView(RightFragment)であるとします。
LeftFragmentはどのアイテムがクリックされたのかをActivityに伝える必要があり、それによりRightFragmentが選択されたアイテムに対応したWebサイトを表示することができます。
- コールバックメソッド用のインタフェース(OnURLSelectedListener)をLeftFragment内に定義
- Activityクラスでインタフェースを実装し、コールバックメソッドをオーバーライド
- LeftFragmentでonAttach()をオーバーライドし、引数のActivityをリスナーオブジェクトにキャスト
- LeftFragmentでItemClickイベントが発生したら、取得したオブジェクトのコールバックメソッドを呼び出す
イベントが発生するFragment内にインタフェースをコールバックメソッドを定義します。
public static class LeftFragment extends ListFragment { //...省略... // アクティビティはこのインターフェイスを実装する必要がある public interface OnURLSelectedListener { public void onURLSelected(Uri uri); } //...省略... }
メソッド内ではRightFragmentをインスタンス化し、メソッドを呼び出すことによってデータの受け渡しを行います。
public class MainActivity extends FragmentActivity implements OnURLSelectedListener{ //...省略... @Override public void onURLSelected(String url) { FragmentManaget fm = getSupportFragmentManager(); RightFragment fragment = (RightFragment) fm.findFragmentById(R.id.right_fragment); fragment.loadUrl(url); } //...省略... }
Activityがリスナーインタフェースを実装していることを保証するので、onAttach()メソッドの引数activityをキャストし、インスタンス変数として登録します。
public static class LeftFragment extends ListFragment { private OnURLSelectedListener listener; //...省略... @Override public void onAttach(Activity activity) { super.onAttach(activity); listener = (OnURLSelectedListener) activity; } //...省略... }
メソッド内ではRightFragmentをインスタンス化し、メソッドを呼び出すことによってデータの受け渡しを行います。
public class LeftFragment extends ListFragment { //...省略... @Override public void onListItemClick(ListView l, View v, int position, long id) { listener.onURLSelected(url[position]); } //...省略... }
Fragmentを追加すると既存のFragmentはActivityの「バックスタック」と呼ばれる領域に保存され、戻るボタンを押下することで戻すことが可能になります。
このバックスタックはFragmentTranscation単位で保存され、タブレット端末のバックキーを押下するか、FragmentManagerクラスのpopBackStack()を呼び出すことで、元に戻る(バックスタックからFragmentを取り出す)ことができます。
バックスタックを利用するには、Fragmentへの操作をFragmentTranscationでまとめる必要があります。
FragmentTranscationでまとめるとは、FragmentTransactionのadd()、remove()あるいはreplace()でFragmentの操作を行い、最終的にcommit()で画面に反映するという今まで通りの手順のことです。
ただし、commitの直前でaddToBackStack()を呼び出し、一連の操作が1つのまとまりだということをFragmentTransactionオブジェクトへ通知する必要があります。
下記のコードが使用例です。
public void onClick(View v){ FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.frameLayout1, new MyFragment()); ft.addToBackStack(null); ft.commit(); }
addToBackStack()の引数にはFragmentTransactionの名前を指定します。
この名前を使用すると、該当するバックスタックまで直接戻ることができます。
名前が不要であればnullを指定します。
上記例ではMyFragemntへのreplace処理が1つのFragmentTransactionとして登録されていますが、2つの操作を1つのFragmentTransactionとして登録することもできます。(例えば、2ペインの画面で両方のFragmentが切り替わる等 )
またFragmentManager.popBackStack()を使用することで、バックスタックの中の特定の場所を指定して戻ることもできます。
下記のコードが使用例です。
- 2番目のバックスタックエントリーまで戻る例
- 指定した名前のFragmentTransactionの1つ前まで戻る例
public void onClick(View v){ FragmentManager fm = getFragmentManager(); BackStackEntry entry = fm.getBackStackEntryAt(1); fm.popBackStack(entry.getId(), 0); }
;
public void onClick(View v){ FragmentManager fm = getFragmentManager(); fm.popBackStack("MyFragment", FragmentManager.POP_BACK_STACK_INCLUSIVE); }
FragmentManager.popBackStack()の第1引数には、FragmentTransactionの名前もしくはFragmentTransactionのIDを指定します。第2引数は0または定数POP_BACK_STACK_INCLUSIVEを指定します。0を指定した場合は、第1引数で指定したFragmentTransactionが表示されるところまで戻り、POP_BACK_STACK_INCLUSIVEを指定した場合は、その指定したFragmentTransactionも取り除くことになります。
そのため、Android 3.0以前のスマートフォンでもFragmentクラスが使えるように、外部ライブラリとしてandroid.support.v4パッケージがリリースされています。
※ポイント※
Android SDK r20以降では、プロジェクトを新規作成すると、android.support.v4パッケージがリンクされた状態でプロジェクトが作成されます。最新の開発環境であれば、手動でandroid.support.v4パッケージをリンクさせる必要はありません。
Fragmentクラスを用いる場合、どちらのパッケージを使用するかで若干コードが変わってくるので注意が必要です。
- Android 3.0以降で使うことのできるandroid.appパッケージ
- サポートライブラリであるandroid.support.v4.appパッケージ
まず、これまで使用してきたクラスのパッケージの違いです。
Android 3.0以降で提供されているクラス | android.support.v4パッケージで提供されているクラス |
---|---|
android.app.Activity | android.support.v4.app.FragmentActivity |
android.app.Fragment | android.support.v4.app.Fragment |
android.app.FragmentManager | android.support.v4.app.FragmentManager |
android.app.FragmentTransaction | android.support.v4.app.FragmentTransaction |
次に、以下の表に代表的なメソッドの違いを示します。
Android 3.0以降 | android.support.v4パッケージ |
---|---|
Activity#getFragmentManager() | FragmentActivity#getSupportFragmentManger() |
android.support.v4.appパッケージを使用してコーディングする際のポイントは、以下の2つです。
- Fragmentを使用したActivityを作成する場合は、ActivityクラスではなくFragmentActivityクラスを使用する
- インポート時に使いたいパッケージを正しくインポートする
メソッドの違いは意識しなくても、Eclipse側でコンパイルエラーとして検出してくれます。