Android タブレットとFragment 【Android Tips】

この記事は2013年5月8日に書かれたものです。内容が古い可能性がありますのでご注意ください。


1
Fragment
Fragmentについて
この章では、Fragmentについて解説していきます。
Fragment概要
Androidアプリの画面設計で意識しておくべき項目の1つとして、複数の異なるタイプのディスプレイでも等しく使いやすい画面設計をするということが挙げられます。これはタブレットとスマートフォンはもちろんのこと、様々なタイプの機種が発売されているAndroidではごく当たり前のことで、例えば、ディスプレイのサイズ、横向きなのか縦向きなのかといった表示の違いです。

従来は、レイアウトを解像度ごとや種別ごとに作成することで対応していましたが、大きく異なる場合にはそれぞれに適した操作性を提供する必要があり、プログラム内で処理を切り分ける必要がありました。このため、プログラムの見通しが悪くなるといった問題が発生していました。

これらの問題を解決する方法として、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)の表示を提供するために拡張されたクラス
Fragmentのライフサイクル
Fragmentのライフサイクルは下記図のようになります。
特定のタイミングで対応するメソッドが呼び出される仕組みとなっています。

各メソッドの概要は下表の通りです。

メソッド名 概要
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()
Fragmentの基礎
それではFragmentを使用した簡単なアプリを作成してみましょう。

  1. FrameLayoutをレイアウトXMLファイルに定義
  2. 実際に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>
    
     
  3. FragmentのレイアウトXMLファイルを作成
  4. 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>
    
     
  5. Fragmentクラスを作成
  6. 次に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ファイルのルートビューとなります。

     
  7. Fragmentクラスを継承した独自クラスをインスタンス化し、FrameLayoutに追加
  8. 最後に、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に配置する処理を完了させています。

 

以上を作成後、実行すると下図のようになります。

UIのないFragment
UIのないFragmentクラスを用いると、Activityにバックグランドの動作を行わせることが可能になります。FragmentにはリソースIDはなく、ユニークな文字列の”タグ”を使用します。これよりFragmentが追加されますが、レイアウトXMLファイルに関連付けされていません。したがって、onCreateView()の呼び出しは行われませんし、メソッドのオーバーライドも不要となります。ActivityからFragmentを取得したい場合は、findFragmentByTag()を用います。

FragmentManager fm = getFragmentManager();
NoUIFragment workFragment = new NoUIFragment();
fm.beginTransaction().add(workFragment, "work").commit();

add()メソッドはオーバーロードされており、第2引数に文字列を指定するとその文字列がタグとなります。

Activityとの連携
getActivity()でActivityにアクセスすることができます。次のようにgetActivity()を用いることで、Activity側のレイアウトXMLファイルで定義しているViewを見つけることができます。

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サイトを表示することができます。

  1. コールバックメソッド用のインタフェース(OnURLSelectedListener)をLeftFragment内に定義
  2. イベントが発生するFragment内にインタフェースをコールバックメソッドを定義します。

    public static class LeftFragment extends ListFragment {
    
        //...省略...
        // アクティビティはこのインターフェイスを実装する必要がある
        public interface OnURLSelectedListener {
            public void onURLSelected(Uri uri);
        }
    
        //...省略...
    }
    
  3. Activityクラスでインタフェースを実装し、コールバックメソッドをオーバーライド
  4. メソッド内では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);
        }
    
        //...省略...
    }
    
  5. LeftFragmentでonAttach()をオーバーライドし、引数のActivityをリスナーオブジェクトにキャスト
  6. Activityがリスナーインタフェースを実装していることを保証するので、onAttach()メソッドの引数activityをキャストし、インスタンス変数として登録します。

    public static class LeftFragment extends ListFragment {
        private OnURLSelectedListener listener;
    
        //...省略...
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            listener = (OnURLSelectedListener) activity;
        }
    
        //...省略...
    }
    
  7. LeftFragmentでItemClickイベントが発生したら、取得したオブジェクトのコールバックメソッドを呼び出す
  8. メソッド内ではRightFragmentをインスタンス化し、メソッドを呼び出すことによってデータの受け渡しを行います。

    public class LeftFragment extends ListFragment {
    
        //...省略...
        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            listener.onURLSelected(url[position]);
        }
    
        //...省略...
    }
    
バックスタック
Activityが実行中の間は、各Fragmentの追加や削除を独立させて操作することができます。

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番目のバックスタックエントリーまで戻る例
  • public void onClick(View v){
        FragmentManager fm = getFragmentManager();
        BackStackEntry entry = fm.getBackStackEntryAt(1);
        fm.popBackStack(entry.getId(), 0);
    }
    

    ;

  • 指定した名前のFragmentTransactionの1つ前まで戻る例
  • 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.support.v4パッケージ)
Fragmentはスマートフォンよりも画面の大きい“タブレット”に対応するために作られたクラスです。つまり、タブレットに対応するため(Fragmentクラスを使うため)には、Androidプロジェクトのターゲットを3.0以降にする必要があります。しかしながら、これではアプリケーションをAndroid 3.0以前のスマートフォンで動作させることができなくなってしまいます。

そのため、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つです。

  1. Fragmentを使用したActivityを作成する場合は、ActivityクラスではなくFragmentActivityクラスを使用する
  2. インポート時に使いたいパッケージを正しくインポートする

メソッドの違いは意識しなくても、Eclipse側でコンパイルエラーとして検出してくれます。

  • このエントリーをはてなブックマークに追加

PAGE TOP