KotlinでFragmentを理解する【Androidアプリ開発】
この記事では、Fragmentの使い方をKotlin言語で学んでいきます。ActivityとFragmentの連携ができるようになるまでの解説となります。ActivityからFragmentのViewへアクセスしたり、値を渡したりする方法を学べます。ActiviyとFragmentの使い分けがしっかりできるようになると、Androidアプリ開発もだいぶラクになります。ぜひこの機会に、Fragmentを使いこなせるようになってください。
この記事でやること
この記事では、図のようにな2つの Fragment を、1つの Activity で操作できるように学習を行なっていきます。Fragmentの理解が目的ですので、できる限り最小限で簡単なプログラミングになるよう心がけました。
なお、本記事で説明しているソースコードは、 AndroidExercise/TryFragment · GitHub で公開中のサンプルプロジェクトの一部のみとなります。詳しくはGitHubのソースコードをご覧になさってください。 AndroidExercise/TryFragment · GitHub
また本記事を書くにあたって、こちらの記事を参考にさせて頂きました。 フラグメント - Androidデベロッパードキュメントガイド Fragment上のonClickとかをFragment内で受け取る - Qiita Fragmentでのイベントハンドリング - 忘れん坊のサンタクロース FragmentのViewをいじってみる - umegusa's blog [Android] アクティビティ内で動的にフラグメントを生成してレイアウトに追加する | rakuishi.com
ベースとなるレイアウトファイルの用意
ArduinoStudioでFragmentのテスト用の新規アプリケーションを作っていきましょう。 まずFragmentを動的に追加するためのコンテナとして、次のような空っぽの状態のレイアウトファイルを用意します。今回は1つだけしかActivityを使わないので、デフォルトで作成されているactivity_main.xmlを修正することにします。ConstraintLayoutのidをcontainerとしていることに注意してください。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" android:id="@+id/container">
</android.support.constraint.ConstraintLayout>
Fragmentの用意
つぎに、Fragmentを作成します。ここでは次のうようなHogeFragmentクラスを用意しました。
class HogeFragment : Fragment() {
private var listener: OnHogeFragmentListener? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentView = inflater.inflate(R.layout.fragment_hoge, container, false)
val addFragment = fragmentView.findViewById<Button>(R.id.addFragment)
addFragment.setOnClickListener {
addFragment()
}
return fragmentView
}
fun addFragment() {
listener?.onHogeFragmentAddFragment()
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnHogeFragmentListener) {
listener = context
} else {
throw RuntimeException("$context must implement OnHogeFragmentListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
interface OnHogeFragmentListener {
fun onHogeFragmentAddFragment()
}
}
ActivityからFragmentを呼び出す
Activityから先ほどのHogeFragmentを呼びだしてみましょう。ActivityのonCreateで表示させたいFragmentをインスタンス化し、supportFragmentManagerを使ってFragmentを追加します。override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val hogeFragment = HogeFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.container, hogeFragment)
transaction.commit()
}
Fragmentにボタンとイベントを追加する
さきほどのHogeFragmentクラスに、ボタンを追加してイベントを設定してみましょう。 次のようにクラスに実装しました。inflaterを使って作成したViewからfindViewByIdで検索し、ボタンの参照を取得してます。また、setOnClickListenerを使って、ボタンのクリックイベントを設定しました。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentView = inflater.inflate(R.layout.fragment_hoge, container, false)
val addFragment = fragmentView.findViewById<Button>(R.id.addFragment)
addFragment.setOnClickListener {
addFragment()
}
return fragmentView
}
java.lang.IllegalStateException: Could not find method onButtonPressed(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton with id 'button' at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.resolveMethod(AppCompatViewInflater.java:424)
Fragmentのレイアウト中のViewにアクセスする
Fragmentのレイアウト中のViewにアクセスしてみましょう。 たとえば、TextViewのレイアウトがあったとして、次のようにしてViewの参照を保持することでアクセスできます。
private var fugaText:TextView? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_fuga, container, false)
fugaText = view.findViewById(R.id.fugaText)
...
}
このFragment全体のコードは次のとおりです。名前をFugaFragmentとしました。
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class FugaFragment : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var listener: OnFugaFragmentListener? = null
private var fugaText:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_fuga, container, false)
fugaText = view.findViewById(R.id.fugaText)
val displayParams = view.findViewById<Button>(R.id.displayParams)
displayParams.setOnClickListener {
displayParams()
}
val finishButton = view.findViewById<Button>(R.id.finishButton)
finishButton.setOnClickListener {
finish()
}
return view
}
fun displayParams() {
fugaText?.text = "$ARG_PARAM1: $param1 / $ARG_PARAM2: $param2"
}
fun finish() {
listener?.onHugaFragmentFinish()
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnFugaFragmentListener) {
listener = context
} else {
throw RuntimeException("$context must implement OnFugaFragmentListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
println("FugaFragment onDetach")
}
interface OnFugaFragmentListener {
fun onHugaFragmentFinish()
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
FugaFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
ActivityからFragmentへ初期値を渡す
ActivityからFragmentへ初期値を渡すには、ファクトリーメソッドを使います。 AndroidStudioでFragmentを新規作成するときに、図のようにinclude fragment factory methods?の項目にチェックを入れます。これでFragmentにファクトリーメソッドが記述された状態で作ることができます。
次がファクトリーメソッドを実装したFragmentになります。companion objectというシングルトンで宣言されているのがファクトリーメソッドです。
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class FugaFragment : Fragment() {
private var param1: String? = null
private var param2: String? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
...
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
FugaFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
さて、ActivityからFragmentを生成し、ファクトリーメソッドに引数を入れて初期値を渡してみましょう。
val hugaFragment = FugaFragment.newInstance("りんご", "バナナ")
このように、ファクトリーメソッドを使うことでActivityからFragmentへ値を渡すことができました。
Fragmentの切り替えとバックスタックへ追加
今回のプロジェクトでは、FugeFragmentのボタンを押すとActivityのonFugeFragmentAddFragmentがイベントされます。onFugeFragmentAddFragmentメソッドの中では、次のようにしてFragmentの切り替えを行なってます。これはバックスタックに追加する処理で、Fragmentを重ねるようなイメージです。
override fun onHogeFragmentAddFragment() {
val hugaFragment = FugaFragment.newInstance("りんご", "バナナ")
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.container, hugaFragment)
transaction.addToBackStack(null) // バックスタックに保存する。呼び出さなければ積まれない。
transaction.commit()
}
ところでActivityとFragmentのやり取りは、このようにインタフェースを介して実現してます。
class HogeFragment : Fragment() {
private var listener: OnHogeFragmentListener? = null
...
fun addFragment() {
listener?.onHogeFragmentAddFragment()
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnHogeFragmentListener) {
listener = context
} else {
throw RuntimeException("$context must implement OnHogeFragmentListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
interface OnHogeFragmentListener {
fun onHogeFragmentAddFragment()
}
}
詳しくはこちらの サンプルプロジェクト をご覧ください。
バックスタックから1つ前のFragmentを取り出す。
バックスタックから1つ前のFragmentを取り出すと、いわゆる画面を「戻る」ことになります。次のようにしてpopBackStack関数を呼び出します。
override fun onHugaFragmentFinish() {
supportFragmentManager.popBackStack()
}
ちなみに、バックスタックから1つ前のFragmentを取り出すと、現在アクティブだったFragmentは非アクティブになり、onDetachが呼ばれ開放さます。
さいごに
FragmentはActivityに似たライフサイクルをもつ事ができるクラスです。ここでは紹介しませんでしたが、ビューを持たないFragmentを作ることも可能です。 Fragmentを使うと、さまざまな処理をFragment内に任せることができるので、Activityが肥大化せずに済みます。 【Kotlin】DialogFragmentでダイアログ表示【Androidアプリ開発】 で紹介しましたが、ダイアログ表示もFragmentを継承したDialogFragmentで実現されてます。▼ こんな記事も書いてます。