フラグメントを用いたマルチデバイスへの対応
フラグメント
を用いてアプリをマルチデバイス対応
にする手順は、以下の通り。
res/layout/activity_main
フォルダに、
画面サイズに応じたレイアウトファイル
(=Android Resource File
)を作成
※Android Resource File
生成時に自動的に配置される- 1.で作成した
レイアウトファイル
にXML
情報を記述- 最初に表示される
フラグメント
ファイルに、
画面サイズの判定を行う変数を定義- 3.で定義した変数の値に応じた分岐処理を、
各フラグメント
ファイルに記述
layoutの設定修飾子
参考1: アプリリリースの概要(表2.)
参考2: 各種の画面サイズのサポート(図4.)
layout-<設定修飾子>
の順番で命名する。
設定修飾子
が複数ある場合、以下の表の上から順に-(ハイフン)
で区切る。
分類 | 設定修飾子 | 内容 |
---|---|---|
画面サイズ | small |
低密度QVGA 320x426[dp]~
|
画面サイズ | normal |
中密度HVGA 320x470[dp]~
|
画面サイズ | large |
中密度VGA 480x640[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
}
}