LoginSignup
2
2

More than 1 year has passed since last update.

【Kotlin研修6日目】フラグメントを用いたマルチデバイス対応、フラグメント間の値渡し

Posted at

フラグメントを用いたマルチデバイスへの対応

フラグメントを用いてアプリをマルチデバイス対応にする手順は、以下の通り。

  1. res/layout/activity_mainフォルダに、
    画面サイズに応じたレイアウトファイル(=Android Resource File)を作成
    Android Resource File生成時に自動的に配置される
  2. 1.で作成したレイアウトファイルXML情報を記述
  3. 最初に表示されるフラグメントファイルに、
    画面サイズの判定を行う変数を定義
  4. 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 水平方向

画面サイズに応じた分岐処理

メインフラグメント(必ず表示)

MenuListFragment.kt(Main)
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 {
                ...   // 画面遷移
            }
        }
    }
}

サブフラグメント(大画面の場合は右半分に表示)

MenuThanksFragment.kt(Sub)
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オブジェクトを利用する。
フラグメントトランザクションを実装する場合は、FragmentManagerFragmentTransaction(後述)を利用する。

画面遷移

参考: 研修3日目
Fragmentクラスは、Intentが継承するActivityクラスを継承していないため、
Activity(を継承する)オブジェクトを利用する際は、
FragmentActivityクラスのactivityプロパティ(Nullable型)を用いる。

サンプルコード

MenuListFragment.kt(遷移元)
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)
        }
    }
}
MenuThanksFragment.kt(遷移先)
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()

サンプルコード

フラグメントの新規追加

MenuListFragment.kt
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()
        }
    }
}

追加したフラグメントの削除

MenuThanksFragment.kt
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を用いたインテントへのデータ格納

サンプルコード

MenuListFragment.kt(追加元)
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を用いたインテントからのデータ取得

サンプルコード

MenuThanksFragment.kt(追加先)
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オブジェクトを利用して値を渡す。

BundleIntentクラスを、IntentActivityクラスを継承しているものの、
FragmentActivityクラスを継承していないため、
フラグメントでこれらのオブジェクトを利用する場合は、
フラグメントactivityプロパティ(Nullable型)を利用する。

サンプルコード

MenuListFragment.kt(遷移元)
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)
            ...
        }
    }
}
MenuThanksFragment.kt(遷移先)
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
    }
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2