Fragmentでシンプルで効率的なUIの開発を
ヤフー矢端です。
FragmentはAndroid3系から使用可能な機能なので、知っている人にとっては今さらという内容になるかもしれません。
しかし日本の市場では2.x系のAndroid端末を保有するユーザーの方も少なくなく、アプリを2.x系以上で対応していた都合から、「Fragmentを知っているけど、まだあまり触ったことがない」という開発者の方もいるのではないかと思います。
そこで本稿では、開発で新たにFragmentを導入する方や、Android初心者の方がFragmentの導入を学んだ後にできる、画面切り替えをFragmentを利用してシンプルに実現していく方法を紹介していきたいと思います。
本稿では、「support-library-v4」を利用したFragmentの実装方法でコードを記載しております。support-libraryについてはAndroid Developerをご覧ください。
導入編
導入の方法についてはいろいろな技術サイトで説明されているので、ここではさくっと説明させていただきます。
Activityに追加する
FragmentActivityというクラスを継承をして実装していきます。
FragmentManager#beginTransaction() メソッドでトランザクションを取得して、Fragmentのインスタンスを追加など変更を加えていき、反映する場合は最後に必ずcommitを呼びます。
FragmentManager manager = getSupportFragmentManager();
TestFragment testFragment = new TestFragment();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add( R.id.test_layout, testFragment, "test" );
transaction.commit();
これでFragmentがActivityに追加され、Fragmentのライフサイクルが機能し始めます。
実装編
Fragmentを利用して、アプリを実装していくにあたって、複数のFragmentを一つのActivity上で扱っていくことになる場合、Activity上で無駄なく管理ができるように実装していくことが必要になっていきます。
Fragmentの階層は浅くを意識する
直接話題には関係ありませんが、何も意識せずにFragmentにFragmentの追加を重ねていき、階層が段々深くなっていくと
- ActivityからのFragmentへの処理の通知をする場合、実装が複雑になる。
- レイアウトの階層が深くなる。
といったことが、実装中に気づかぬところで発生してしまうかもしれません。
レイアウトの階層が深くなるとStackOverFlowErrorが発生します。Android4系以上では発生しにくくなりますが、深いレイアウトはあまりよいとはいえません。Fragment自体の階層を減らすか、mergeタグを活用することで改善することができます。
そこで最初に、ActivityからFragment。FragmentからFragmentへの、親から子へのイベントの通知をシンプルに実装する方法について紹介していきます。
実装編:基底クラスを用意する
アプリで使うFragmentの基底クラスを用意して、その基底クラスを継承するようにするだけで、Fragmentを扱うクラスをより効率的に扱うことができます。
例えばActivity上に追加した全Fragmentに対して、表示の更新処理を通知したい場合、よくあるのはFragmentManagerからfindFragmentByIdなどで追加済みのFragmentのインスタンスを取得して、それぞれのインスタンスのメソッドを呼んであげなければなりません。
FragmentManager manager = getSupportFragmentmanager();
//transactionで追加したときに指定したタグかviewのIDから取得をする方法
Test1Fragment test1 = (Test1Fragment) manager.findFragmentByTag( "tag" );
Test2Fragment test2 = (Test2Fragment) manager.findFragmentByTag( R.id.viewID );
//各フラグメントを更新
test1.reload();
test2.reload();
そこで基底クラスを用意してあげます。
基底のクラスを用意
public abstract class BaseFragment extends Fragment {
public void reload() {}
}
FragmentManagerには、現在保持しているFragmentをListで返す getFragments() というメソッドがあります。上記で行ったイベントの通知が、下のように実装できます。
FragmentManager manager = getSupportFragmentmanager();
List<Fragment> list = manager.getFragments();
for ( Fragment fragment : list ) {
( (BaseFragment) fragment ).reload();
}
この例だと行数はほとんど変わりませんが、これがActivity上のFragmentがさらに増えていった場合、その差がはっきり現れます。あとは基底クラスを継承したクラスでメソッドをオーバーライドして処理を実装するだけです。
共通の基底クラスを用意しておくと、ほかにも一つのレイアウト上で複数のFragmentを切り替えている場合に、そのFragmentのインスタンスのチェックや、クラスのキャストをする手間を省いて、基底クラスのメソッドを呼び出すだけになるので、ActivityからのFragmentへのイベント通知の実装がシンプルになります。
実装編:FragmentTransactionを使いこなす
FragmentManagerから追加・削除など変更を行う場合に必要なFragmentTransactionですが、この機能を理解すると、FragmentTabHostやFragmentPagerAdapterと同じようなことが独自に実装できるようになります。
今回はFragmentTabHost のような 機能を実装しながらtransactionについて説明していきます。
FragmentTabHostとは
タブをタップすることでFragmentを切り替えられるようになります。実装の仕方は調べていただければわかると思います。
簡単に実装できる分、多少制約があったりもします。
FragmentTabHostのような機能を実装してみる
制御をするためのクラスを一つ作ります。コンストラクタや入力チェックなどは省略してあります。
省略した部分では以下のようなメソッドが実装されています。
- コンストラクタ:FragmentManagerのインスタンスと表示するViewのIDをメンバに渡す
- HashMapにFragmentとそれに関連付けるキー値を渡してメンバで保持する。
//キーに関連付けたFragmentの表示する
public void change( final String aKey ) {
final Fragment currentFragment = _mManager.findFragmentById( _mId );
final FragmentTransaction transaction = _mManager.beginTransaction();
if ( currentFragment == null ) {
//初回は指定したviewIDにFragmentを追加する
for ( Map.Entry<String, Fragment> entry : _mMap.entrySet() ) {
transaction.add( _mId, entry.getValue() );
}
}
//表示状態を切り替える
for ( Map.Entry<String, Fragment> entry : _mMap.entrySet() ) {
final Fragment fragment = entry.getValue();
if ( aKey.equals( entry.getKey() ) ) {
//fragmentを表示状態に変更する
transaction.show( fragment );
} else {
//fragmentを非表示状態にする
transaction.hide( fragment );
}
}
//変更の反映
transaction.commitAllowingStateLoss();
}
あとは、表示したいFragmentのインスタンスを追加し、ボタンなどのイベントと関連したページのキーを渡すようにするだけで以下のように表示を切り替えることができます。
表示しているFragmentは、page1から順に アナログ時計・webView・カレンダーをレイアウトに配置しただけのFragmentになります。
FragmentTransactionで加えられる変更とその挙動
上記で、FragmentTabHostのような機能を実装しました。画面を切り替える際に行っていたことは
- FragmentManager#beginTransaction()でトランザクションを開始
- 初回実行時は指定されたviewIDにFragmentを追加する。
- キーで指定されたフラグメントを表示状態に変更。
- キーで指定されたフラグメント以外を非表示状態に変更。
- commitで変更を反映。
になります。
上記で行った変更と、ほかによく使うわれるであろうremoveとreplaceの計5つの挙動について紹介します。
- add:(ViewID, Fragment, TAG)
ViewIDまたはタグと関連付けてFragmentを追加する - remove(Fragment)
Fragmentを削除する。 - replace(ViewID, Fragment, TAG)
ViewIDまたはタグと関連付けてFragmentを追加する。
addと違う点は、指定したViewIDまたはタグにすでにFragmentが存在する場合は、そのIDまたはタグに属するFragmentをすべてremoveしてからaddするといった挙動になる。 - show(Fragment)
存在するfragmentを表示状態に変更する。Fragmentのライフサイクルは変化しない。 - hide(Fragment)
存在するfragmentを非表示状態に変更する。Fragmentのライフサイクルは変化しない。
環境(端末リソース)にやさしい画面変更を実現していく
FragmentTransactionでは上記のような変更のほかに、addToBackStackというメソッドで現在の状態を保存して、バックキーが押されるか、FragmentManager#popBackStackというメソッドを呼び出す等で、元の状態を復元させるといったことも可能です。
FragmentTransaction transaction = manager.beginTransaction();
transaction.add( R.id.test_layout, testFragment, "test" );
//バックスタックに変更前の状態を追加する
transaction.addToBackStack( "first" );
transaction.commit();
これらのTransactionの仕組みを利用して
- 上記の例のように2~3つの要素を切り替えるだけであれば今回のように show と hide で表示を切り替える。
- 毎回生成したFragmentのインスタンスを追加・削除して表示を切り替えるのであれば、前回表示のFragmentを削除して置き換えてくれる replace で変更をする。
- バックキーが押されたら、以前表示していた状態に戻す。というのであればTransaction毎にaddToBackStackで状態を保存して戻ってこれるようにする。
といったような表示切り替えが簡単に実装できます。使えるメモリ量の限られたアプリを意識しつつ、実現したいUI/UXに合わせて無駄のないFragmentTransactionの実装をしていきましょう。
最後に
今回は、シンプルな実装でFragmentをレイアウトに適応する場面を主に説明をさせていただきました。これからのAndroidアプリ開発においてFragmentはますます必要不可欠なものになっていくと思います。
今回紹介させていただいたものはほんの一部にすぎませんが、少しでもFragmentを扱う開発の手助けになれば幸いです。
iOS6から導入されたAuto Layout問題に挑戦してみない?
ヤフー山口恭兵さんからのiOSの問題が出題中です。AndroidもiOSもOK!というエンジニアの皆さん、ぜひ挑戦してみてくださいね。
Autolayoutを設定して画面のサイズ・向きの変化に対応できるレイアウトを作成する問題です。
- 締切4月14日(月)AM10:00まで
- 問題挑戦はこちらから
CodeIQコード銀行にあなたのコードを預けてみませんか?
- CodeIQコード銀行ではあなたのコードを財産と考えます。
- お預かりいただいたコードは、CodeIQコード銀行がしっかり評価し、フィードバックいたします。
- 当コード銀行にお預けいただいたコードは、企業がみてスカウトをかける可能性があります。
- 転職したい方や将来転職することを考えている方で、今の自分のスキルレベルを知りたい方はぜひ挑戦してみてください。
- 企業からスカウトがきたら困る人は挑戦しないでください。
興味を持った方はこちらからチャレンジを!
ヤフー株式会社に勤務。ニュースサービスでAndroidアプリの開発に従事。現在、ニュースアプリ「Yahoo!ニュース」の開発を行う。