10インチ用処理
10インチタブレット用の画面構成がひととおりできた状態で処理を記述していきます。
処理の大きな流れ
今のままでは、リストをタップしたら以下のように注文完了画面が起動してしまいます。
これでは、まったく意味がありません。そこで、右側の余白に注文完了が表示されるようにしていきます。スマホサイズとの処理の違いを図解すると以下のようになります。
まず、スマホサイズではひとつのアクティビティにひとつのフラグメントがのっています。リストアクティビティにはリストのフラグメントがのり、注文完了アクティビティには注文完了フラグメントがのっています。また、リストをタップすると、注文完了アクティビティが起動し、したがって、注文完了フラグメントが動き出します。データの受け渡しもインテントを使用します。
ところが、10インチになると一画面となり、以下のような動きになります。
初期表示では図4のように左側にリストが表示されているだけで、右側は空欄です。リストをタップしたら、この空欄であるFrameLayoutに注文完了フラグメントを追加するのです。「リストに戻る」ボタンをタップしたら、追加されたフラグメントを削除します。
このようにすることで、リストをタップするたびに右側に注文完了メッセージが表示されたような動作になります。
こういったフラグメントの追加削除を行う先として、画面部品を上に上に重ねて表示できるFrameLayoutは最適なのです。
画面判定
さて、大きな処理の流れがわかったところで、ソースコードを記述していきます。
まず、大前提として、フラグメント内で、画面サイズに応じて分岐が生じるということです。例えば、リストフラグメントでは、タップされた際に通常サイズ画面の場合は注文完了アクティビティを起動する必要がありますが、10インチタブレットの場合はフラグメントを追加する必要があります。注文完了画面も同様に分岐が生じます。
そのため、分岐の基準となる画面サイズ判定フラグを用意することにします。
MenuListFragmentに、以下のようにフィールドとメソッドを追加してください。
public class MenuListFragment extends Fragment { private boolean _isLayoutXLarge = true; // (1) @Override public void onActivityCreated(Bundle savedInstanceState) { // (2) super.onActivityCreated(savedInstanceState); View menuThanksFrame = _parentActivity.findViewById(R.id.menuThanksFrame); // (3) if(menuThanksFrame == null) { // (4) _isLayoutXLarge = false; // (5) } } ~省略~ }
10インチタブレットかそれ以外かを判定するフラグフィールドとして_isLayoutXLargeを用意します(リスト2の(1))。初期値としてtrueとしておきます。つまり、初期値は10インチタブレットです。
そして、実際どのサイズなのかを判定する処理をonActivityCreated()メソッド内に記述します(リスト2の(2))。このメソッドは前回のフラグメントのライフサイクルの図でも出てきましたが、フラグメントが生成される一番最後に実行されるメソッドで、メソッド名の通り、アクティビティが生成されたのちに実行されます。したがって、アクティビティの状態に依存する処理はここに記述します。
今回、アクティビティ画面上にidがmenuThanksFrameの画面部品、つまり、フラグメント追加用のFrameLayoutが存在するかどうかで、10インチかどうか判定しますので、このメソッドにその処理を記述しています。
まず、_parentActivityのfindViewById()でmenuThanksFrameの画面部品を取得します(リスト2の(3))。そして、これがnullだったら(リスト2の(4))通常画面サイズと判定し、フラグをfalseにします(リスト2の(5))。
リストタップ時処理
画面サイズ判定ができたところで、このフラグを使ってリストタップ時の処理を記述します。これは、MenuListFragment内のリスナクラスListItemClickListenerのonItemClick()メソッド内の改造です。以下のようにソースコードを記述してください。
public class MenuListFragment extends Fragment { ~省略~ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Map<String, String> item = (Map<String, String>) parent.getItemAtPosition(position); // (1) String menuName = item.get("name"); // (1) String menuPrice = item.get("price"); // (1) if(_isLayoutXLarge) { // (2) Bundle bundle = new Bundle(); // (3) bundle.putString("menuName", menuName); // (4) bundle.putString("menuPrice", menuPrice); // (4) FragmentManager manager = getFragmentManager(); // (5) FragmentTransaction transaction = manager.beginTransaction(); // (6) MenuThanksFragment menuThanksFragment = new MenuThanksFragment(); // (7) menuThanksFragment.setArguments(bundle); // (8) transaction.replace(R.id.menuThanksFrame, menuThanksFragment); // (9) transaction.commit(); // (10) } else { // (11) Intent intent = new Intent(_parentActivity, MenuThanksActivity.class); // (12) intent.putExtra("menuName", menuName); // (12) intent.putExtra("menuPrice", menuPrice); // (12) startActivity(intent); // (12) } } } }
(1)と(12)はもともと記述されていたソースコードです。
ソースコードが記述できた時点で、一度アプリを再起動してみてください。リストをタップすると、右側に注文完了フラグメントが表示されるはずです。ただし、MenuThanksFragmentの方はまだ改造していませんので、注文したメニュー名や値段は表示されませんし、「リストに戻る」ボタンをタップするとアプリ自体が終了してしまいうので注意してください。
フラグメントの追加
では、追記したソースコードの解説をしていきます。_isLayoutXLargeフラグを使って分岐を行っているのが(2)です。_isLayoutXLargeがfalse、つまり通常サイズの画面の場合は、前回記述した処理となるので、もともとあった(12)のソースがelse以下に移動しています(リスト3の(11))。ここでは、_isLayoutXLargeがtrue、つまり、10インチ画面の場合である(3)~(10)の記述に注目します。これが、menuThanksFrameのFrameLayoutにフラグメントが追加される処理です。
アクティビティへのフラグメントの追加処理は以下の手順です。
1) フラグメントの管理を行うクラスFragmentManagerオブジェクトを取得します。これは、getFragmentManager()メソッドを使用します(リスト3の(5))。
2) FragmentManagerからFragmentTransactionオブジェクトを取得します。フラグメントの追加削除は、あたかもデータベースへのデータの追加削除のように、トランザクションという考え方を採用しています。これを制御するクラスがFragmentTransactionクラスです。このオブジェクトは、FragmentManagerのbeginTransaction()メソッドで取得します(リスト3の(6))。
3) 追加するFragmentオブジェクトを生成、もしくは取得します(リスト3の(7))。この場合は新規追加なので、MenuThanksFragmentクラスをnewしています。
4) フラグメントの追加・削除・置き換え処理を行います(リスト3の(9))。これらは、FragmentTransactionクラスのメソッドを使います。追加する場合はadd()、削除する場合はremove()です。この削除と追加を同時に行う処理、つまり置き換え処理がreplace()です。add()もreplace()も引数を2個必要とし、第1引数は追加先のレイアウト画面部品です(これを「コンテナ」といいます)。第2引数は追加するFragmentオブジェクトです。removeの引数は、削除するFragmentオブジェクトのみです。
5) コミットします(リスト3の(10))。これは、FragmentTransactionクラスのメソッドcommit()を使います。手順2)で説明したとおり、フラグメント処理はトランザクションという考え方を採用していますので、このcommit()メソッドが実行されるまで、フラグメントの状態は反映されません。
ところで、これから追加するフラグメントにデータを渡したい場合があります。今回では、メニュー名と金額を渡す必要があります。その処理がリスト3の(3)、(4)、(8)です。
Androidではこういう引継ぎデータを管理するクラスとして、Bundleクラスというのが用意されています。実は、インテントを使用してアクティビティ間のデータ受け渡しを行う際、Intentの内部ではこのBundleクラスが使われています。そのBundleクラスをnewします(リスト3の(3))。生成したBundleオブジェクトにデータを格納します(リスト3の(4))。そして、生成したFragmentオブジェクトのsetArguments()メソッドを使用して、このBundleオブジェクトを渡します(リスト3の(8))。これで、追加するフラグメントにデータを渡すことができます。
なお、Intentの内部でもこのBundleオブジェクトが使われていることから、リスト3のソースコードは、Bundleオブジェクトへのデータ格納を共通化し、if-else内を以下のようにもう少しシンプルに記述することができます。
Bundle bundle = new Bundle(); // (1) ~省略~ if(_isLayoutXLarge) { ~省略~ menuThanksFragment.setArguments(bundle); // (2) ~省略~ } else { Intent intent = new Intent(_parentActivity, MenuThanksActivity.class); intent.putExtras(bundle); // (3) startActivity(intent); }
(1)のように、Bundleオブジェクトへのデータ格納をif-elseの外側で行います。(2)はリスト3と同じです。違いは(3)です。リスト3ではputExtra()メソッドを使って各々データを格納していましたが、IntentクラスのputExtras()メソッドを使用すれば、Bundleオブジェクトを直接渡すことができます。
ダウンロードサンプルはこのように記述していますので、参考にしてください。