フラグメントを用いたマルチデバイスへの対応
フラグメントを用いてアプリをマルチデバイス対応にする手順は、以下の通り。
res/layout/activity_mainフォルダに、
画面サイズに応じたレイアウトファイル(=Android Resource File)を作成
※Android Resource File生成時に自動的に配置される- 1.で作成した
レイアウトファイルにXML情報を記述- 最初に表示される
フラグメントファイルに、
画面サイズの判定を行う変数を定義- 3.で定義した変数の値に応じた分岐処理を、
各フラグメントファイルに記述
layoutの設定修飾子
参考1: アプリリリースの概要(表2.)
参考2: 各種の画面サイズのサポート(図4.)
layout-<設定修飾子>の順番で命名する。
設定修飾子が複数ある場合、以下の表の上から順に-(ハイフン)で区切る。
| 分類 | 設定修飾子 | 内容 |
|---|---|---|
| 画面サイズ | small |
低密度QVGA320x426[dp]~
|
| 画面サイズ | normal |
中密度HVGA320x470[dp]~
|
| 画面サイズ | large |
中密度VGA480x640[dp]~
|
| 画面サイズ | xlarge |
中密度HVGA以上720x960[dp]~
|
| 画面アスペクト | long |
Wの付く幅が長い画面 |
| 画面アスペクト | long |
Wの付かない幅が長くない画面 |
| 画面の向き | port |
垂直方向 |
| 画面の向き | land |
水平方向 |
画面サイズに応じた分岐処理
メインフラグメント(必ず表示)
class MenuListFragment: Fragment() {
// 10-inchの画面かどうか判定するフラグ
private var _isLayoutXLarge = true
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
...
// アクティビティ内のFrameLayout(View)を取得
val menuThanksFrame = activity?.findViewById<View>(R.id.menuThanksFrame)
// 指定したView(FrameLayout)が同一アクティビティ内に存在しない場合
// -> 10-inchでない場合
if (menuThanksFrame == null) {
_isLayoutXLarge = false
}
}
// ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
private inner class ListItemClickListener: AdapterView.OnItemClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// 10-inchの画面である場合
if (_isLayoutXLarge) {
... // フラグメントトランザクション(後述)
}
// 10-inchの画面でない場合
else {
... // 画面遷移
}
}
}
}
サブフラグメント(大画面の場合は右半分に表示)
class MenuThanksFragment: Fragment() {
// 10-inchの画面かどうか判定するフラグ
private var _isLayoutXLarger = true
override fun onCreate(savedInstanceState: Bundle?) {
...
// 10-inchの判定を行うためのフラグメント
// fragmentManager: FragmentManagerオブジェクトを表すプロパティ
// FragmentManager: アクティビティが保有するフラグメントを管理するクラス
// FragmentManager.findFragmentById(Id:): 指定したID(R値)に一致するフラグメントの取得
// id: レイアウトファイル(XML)で記述したフラグメントのID(R値)
val menuListFragment = fragmentManager?.findFragmentById(R.id.fragmentMenuList)
// 指定したフラグメントが同一アクティビティ内に存在しない場合
// -> 10-inchでない場合
if (menuListFragment == null) {
_isLayoutXLarger = false
}
}
// フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// 10-inchの画面である場合(=同一アクティビティ内)
if (_isLayoutXLarger) {
... // Bundleオブジェクト(argumentsプロパティ)を通じてデータ取得
}
// 10-inchの画面でない場合(=別のアクティビティ)
else {
... // Intentオブジェクトを通じてデータ取得
}
}
FragmentManager
参考: フラグメントマネージャー
アクティビティが保有するフラグメントを管理するクラス。
フラグメント間の画面遷移とフラグメントトランザクション
画面遷移を実装する場合は、Intentオブジェクトを利用する。
フラグメントトランザクションを実装する場合は、FragmentManagerとFragmentTransaction(後述)を利用する。
画面遷移
参考: 研修3日目
Fragmentクラスは、Intentが継承するActivityクラスを継承していないため、
Activity(を継承する)オブジェクトを利用する際は、
FragmentActivityクラスのactivityプロパティ(Nullable型)を用いる。
サンプルコード
class MenuListFragment : Fragment() {
...
// ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
private inner class ListItemClickListener: AdapterView.OnItemClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onItemClick(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
// 遷移元と遷移先を紐づけるIntentオブジェクトの生成
// -> フラグメントのアクティビティオブジェクトは`activity`プロパティで表される
val intent2MenuThanks = Intent(activity, MenuThanksActivity::class.java)
// 遷移先アクティビティの起動
startActivity(intent2MenuThanks)
}
}
}
class MenuThanksFragment : Fragment() {
...
// "タップ"イベントを検知するするリスナクラス(リスナ)
private inner class ButtonClickListener: View.OnClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onClick(view: View?) {
// アクティビティの終了
// -> activityプロパティを中継してfinish()メソッドを利用
activity?.finish()
}
}
}
フラグメントトランザクション
参考: フラグメントトランザクション
FragmentManagerによって管理されるフラグメントの追加・削除など、
フラグメントに関する複数の処理をグループ化した1つの処理。
FragmentTransactionクラス
フラグメントトランザクションを制御するクラス。
FragmentTransactionオブジェクトの取得
フラグメントトランザクションを制御するのはFragmentTransactionクラスであるが、
フラグメントを管理するのはFragmentManagerであるため、
FragmentManagerにあたるFragmentクラスのfragmentManagerプロパティを利用する。
定義
FragmentManager?.beginTransaction()
編集するフラグメントの定義
// 新規追加する場合
val newFragment = newFragment()
// 他のフラグメントを指定する場合
val editedFragment = FragmentManager?.findFragmentById(R.id.fragmentMenuList)
// 自身のフラグメントを指定する場合
// -> 記述不要
フラグメントトランザクションの編集
フラグメントの追加
FragmentTransaction?.add(
containerViewId: Int,
fragment: Fragment!
): FragmentTransaction!
// パラメータ
// containerViewId: 追加先のレイアウト部品のID(R値)
// fragment: 追加するフラグメントオブジェクト
フラグメントの置換
FragmentTransaction?.replace(
containerViewId: Int,
fragment: Fragment!
): FragmentTransaction!
// パラメータ
// containerViewId: 置換先のレイアウト部品のID(R値)
// fragment: 置換するフラグメントオブジェクト
フラグメントの削除
FragmentTransaction?.remove(
fragment: Fragment!
): FragmentTransaction!
// パラメータ
// fragment: 削除するフラグメントオブジェクト
編集したフラグメントトランザクションの反映(コミット)
定義
FragmentTransaction?.commit()
サンプルコード
フラグメントの新規追加
class MenuListFragment : Fragment() {
...
// ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
private inner class ListItemClickListener: AdapterView.OnItemClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// フラグメントトランザクションの編集開始
val transaction = fragmentManager?.beginTransaction()
// 追加するフラグメントオブジェクトの生成
val menuThanksFragment = MenuThanksFragment()
// フラグメントの置換
transaction?.replace(R.id.menuThanksFrame, menuThanksFragment)
// 編集したフラグメントトランザクションを反映(コミット)
transaction?.commit()
}
}
}
追加したフラグメントの削除
class MenuThanksFragment : Fragment() {
...
// "タップ"イベントを検知するするリスナクラス(リスナ)
private inner class ButtonClickListener: View.OnClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onClick(view: View?) {
// フラグメントトランザクションの編集開始
val transaction = fragmentManager?.beginTransaction()
// フラグメントの削除
transaction?.remove(this@MenuThanksFragment)
// 編集したフラグメントトランザクションを反映(コミット)
transaction?.commit()
}
}
}
フラグメントトランザクションにおけるフラグメント間の値渡し
追加するフラグメントのBundle型であるargumentsプロパティを利用して値を渡す。
遷移元フラグメントでは、IntentオブジェクトがもつBundleオブジェクトに直接データを格納する。
遷移先フラグメントでは、Bundleオブジェクトのデータを、
Fragmentクラスのargumentプロパティを用いてデータを取得する。
Bundle
参考: Bundle
キーと値を格納したMap<String, Any>型のデータ構造をもつコレクション。
このデータ構造を用いて、各種データに名前を付けて管理することができる。
Bundleを用いたインテントへのデータ格納
サンプルコード
class MenuListFragment: Fragment() {
...
// ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
private inner class ListItemClickListener: AdapterView.OnItemClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
...
// Bundleオブジェクトの生成
val bundle = Bundle()
// Bundleオブジェクトへのデータ格納
bundle.putString("menuName", menuName)
bundle.putString("menuPrice", menuPrice)
...
// 遷移先フラグメントオブジェクトの生成
val menuThanksFragment = MenuThanksFragment()
// 遷移先オブジェクトのargumentsプロパティにBundleオブジェクトを代入
// -> 遷移先ではargumentsプロパティを用いてデータを取得
menuThanksFragment.arguments = bundle
}
}
}
Bundleを用いたインテントからのデータ取得
サンプルコード
class MenuThanksFragment: Fragment() {
// フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
// データが格納されるBundleオブジェクト
// -> 画面サイズに応じた分岐処理を行えるよう初期化せず定義
val extras: Bundle?
// Bundleオブジェクトにデータを格納
// arguments: データが格納されたBundleオブジェクト
// <- 追加元でargumentsプロパティにデータを格納済み
extras = arguments
// キーを指定して値を取得
val menuName = extras?.getString("menuName")
val menuPrice = extras?.getString("menuPrice")
...
}
画面遷移におけるフラグメント間の値渡し
参考: 研修3日目
Intentオブジェクトを利用して値を渡す。
BundleはIntentクラスを、IntentはActivityクラスを継承しているものの、
FragmentはActivityクラスを継承していないため、
フラグメントでこれらのオブジェクトを利用する場合は、
フラグメントのactivityプロパティ(Nullable型)を利用する。
サンプルコード
class MenuListFragment : Fragment() {
...
// ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
private inner class ListItemClickListener: AdapterView.OnItemClickListener {
// "タップ"イベント検知時の処理(イベントハンドラ)
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// Bundleオブジェクトの生成
val bundle = Bundle()
// Bundleオブジェクトへのデータ格納
bundle.putString("menuName", menuName)
bundle.putString("menuPrice", menuPrice)
// 画面遷移を実現するIntentオブジェクトの生成
// -> FragmentクラスはActivityクラスを継承していないため、 Fragment.activityプロパティを指定
val intent2MenuThanks = Intent(activity, MenuThanksActivity::class.java)
// Bundleオブジェクトへのデータの格納
intent2MenuThanks.putExtras(bundle)
...
}
}
}
class MenuThanksFragment: Fragment() {
// フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
// データが格納されるBundleオブジェクト
// -> 画面サイズに応じた分岐処理を行えるよう初期化せず定義
val extras: Bundle?
// 遷移元と遷移先を紐づけるIntentオブジェクト
// -> FragmentクラスはActivityクラスを継承していないため、
// activityプロパティ(Nullable型)を中継してintentプロパティを利用
// intent: Intentオブジェクト(Activityクラスのプロパティ)
val intent = activity?.intent
// データが格納されたBundleオブジェクト
// extras: Intentオブジェクトに格納されたキーと値を格納するBundleオブジェクト
extras = intent?.extras
}
}