学ぶこと
- Data Binding ライブラリを使用して、
findViewById()
への非効率な呼び出しを排除する方法 - XML からアプリデータに直接アクセスする方法
すること
アプリ概要
このコードラボでは、AboutMe アプリからデータバインディングを使用するようにアプリを変更します。完了すると、アプリはまったく同じに見えます。
AboutMe アプリの機能は次の通りです:
ユーザがアプリを開くと、アプリには名前、ニックネームを入力するためのフィールド、Done ボタン、star imgage、スクロール可能なテキストが表示されます。
ユーザはニックネームを入力して Done ボタンをタップできます。編集可能なフィールドとボタンは、入力されたニックネームを表示する TextView に置き換えられます。
前回のコードラボで作成したコードを使用するか、Github から AboutMeDetaBinding-Starter
コードをダウンロードできます。
Data binding を使用して findViewById() を削除する
前回のコードラボで記述したコードは、findViewById()
関数を使用して View への参照を取得します。
View が作成または再作成後、findViewById()
を使用して View を検索するたびに、Android システムは実行時に View 階層をたどって View を見つけます。アプリの View 数が少ない場合、これは問題ではありません。ただし、プロダクションアプリでは、レイアウトに数十の View が含まれる場合があり、最適なデザインであってもネストされた View があります。
TextView
を含む ScrollView
を含む Linear Layout
を考えてください。大規模または深い View 階層の場合、View を見つけるのに多くの時間がかかり、ユーザのアプリの速度が著しく低下する可能性があります。
変数に View をキャッシュすると役立つ場合がありますが、各ネームスペースで、View ごとに変数を初期化する必要があります。多くの View と複数の Activity があるため、これもまた加算されます。
1つの解決策は、各 View への参照を含むオブジェクトを作成することです。Binding
オブジェクトと呼ばれるこのオブジェクトは、アプリ全体で使用できます。この手法は data binding と呼ばれます。アプリのバインディングオブジェクトが作成されると、View の階層を走査したりデータを探索したりすることなく、バインディングオブジェクトを介して View やその他のデータにアクセスできます。
Data binding には次の利点があります:
コードは、
findByView()
を使用するコードよりも短く、読みやすく、保守が容易です。データと View は明確に分離されています。このデータバインディングの利点は、このコースの後半でますます重要になります。
Android システムは、View 階層を一回だけトラバースして、各 View を取得します。これは、ユーザがアプリを操作している実行時ではなく、アプリの起動時に行われます。
View にアクセスするための type safety を取得します。(タイプセーフとは、コンパイラーがコンパイル中に型を検証し、変数に間違った型を割り当てようとするとエラーがスローされることを意味します。)
このタスクでは、データバインディングを設定し、データバインディングを使用して、findViewById()
への呼び出しをバインディングオブジェクトへの呼び出しに置き換えます。
データバインディングを有効にする
データバインディングを使用するには、Gradle file でデータバインディングを有効にする必要があります。デフォルトでは有効になっていません。これは。データバインディングによってコンパイル時間が長くなり、アプリの起動時間に影響する可能性があるためです。
AboutMe アプリを開きます。
build.gradle(Module: app)
ファイルを開きます。android セクション内の右中括弧の前に、
buildFeatures
セクションを追加し、dataBinding
をtrue
に設定します。
buildFeatures { dataBinding true }
プロンプトが表示されたら、プロジェクトを Sync します。メッセージが表示されない場合は、File > Sync Project with Gradle Files を選択します。
アプリは実行できますが、変更は表示されません。
データバインディングで使用できるようにレイアウトファイルを変更する
データバインディングを使用するには、XML レイアウトを <layout>
タグでラップする必要があります。これにより、ルートクラスは ViewGroup ではなく、ViewGroup と View を含むレイアウトになります。
バインディングオブジェクトは、レイアウトとその中の View について知ることができます。
activity_main.xml
ファイルを開きます。Code タブに切り替えます。
<LinearLayout>
を囲む最も外側のタグとして<layotu></layout>
を追加します。
<layout> <LinearLayout ... > ... </LinearLayout> </layout>
- Code > Reformat code を選択して、コードのインデントを修正します。
layout の namespace 宣言は、最も外側のタグにある必要があります。
<LinearLayout>
から名前空間宣言を切り取り、<layout>
タグに貼り付けます。開始の<layout>
タグは次のように表示され、<LinearLayout>
タグには view property のみが含まれている可能性があります。
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
- アプリを実行して、これが正しく行われたことを確認します。
main activity で binding object を作成する
Binding object への参照を main activty に追加し、それを使用して view にアクセスできるようにします。
MainActivity.kt
ファイルを開きます。onCreate()
の前のトップレベルで、Binding Object の変数を作成します。この変数は慣習的にbinding
と呼ばれます。
binding
のタイプである ActivityMainBinding
クラスは、この main activity 専用にコンパイラによって作成されます。名前は、レイアウトファイルの名前、つまり activity_main + Binding
から派生します。
private lateinit var binding: ActivityMainBinding
- Android Studio でプロンプトが表示されたら、
ActivityMainBinding
をインポートします。プロンプトが表示されない場合は、ActivityMainBinding
をクリックし、Option + Enter
を押して、この欠落しているクラスをインポートします。(キーボードショートカットの詳細についてはこちら)
import
ステートメントは、次のようになります。
import com.example.android.aboutme.databinding.ActivityMainBinding
次に、現在の setContentView()
関数を次のことを行う命令に置き換えます。
Binding Object を作成します。
DataBindingUtil
クラスのsetContentView()
関数を使用して、activity_main.xml
レイアウトをMainActivity
に関連付けます。このsetContentView()
関数は、view のデータバインディング設定も処理します。onCreate()
で、setContentView()
呼び出しを次のコード行に置き換えます。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
DataBindingUtil
をインポートします。
import androidx.databinding.DataBindingUtil
Binding Object を使用して、findViewById() へのすべての呼び出しを置き換えます
これで、findViewById()
へのすべての呼び出しを、バインディングオブジェクト内の View への参照に置き換えることができます。バインディングオブジェクトが生成されると、コンパイラはレイアウト内の View の ID からバインディングオブジェクト内の View の名前を生成し、キャメルケースに変換します。したがって、例えば、done_button
はバインディングオブジェクトでは doneButton
になり、nickname_edit
は nicknameEdit
になります。
onCreate()
で、findViewById()
を使用してdone_button
を検索するコードを、バインディングオブジェクトのボタンを参照するコードに置き換えます。
このコードを置き換えます:
findViewById<Button>(R.id.done_button)
-> binding.doneButton
onCreate()
でクリックリスナーを設定するための完成コードは、次のようになります。
binding.doneButton.setOnClickListener { addNickname(it) }
addNickname()
関数のfindViewById()
へのすべての呼び出しに対して同じことを行います。findViewById<View>(R.id.id_view)
のすべての箇所をbinding.idView
で置き換えます。これは次の方法で行います:editText
およびnicknameTextView
変数の定義と、findViewById()
の呼び出しを削除します。これは Error になります。binding
オブジェクトからnicknameText
、nicknameEdit
およびdoneButton
を取得して、エラーを修正します。view.visibility
をbinding.doneButton.visibility
に置き換えます。渡された View の代わりにbinding.doneButton
を使用すると、コードの一貫性が向上します。
結果は次のコードです:
binding.nicknameText.text = binding.nicknameEdit.text binding.nicknameEdit.visibility = View.GONE binding.doneButton.visibility = View.GONE binding.nicknameText.visibility = View.VISIBLE
機能に変更はありません。オプションで、
View
パラメータを削除し、view
のすべての使用を更新して、この関数内でbinding.doneButton
を使用することができます。nicknameText
にはString
が必要で、nicknameEdit.text
はEditable
です。データバインディングを使用する場合、Editable
を明示的にString
に変換する必要があります。
binding.nicknameText.text = binding.nicknameEdit.text.toString()
グレー表示されたインポートを削除することができます。
apply{}
を使用して関数を Kotlinize します。
binding.apply { nicknameText.text = nicknameEdit.text.toString() nicknameEdit.visibility = View.GONE doneButton.visibility = View.GONE nicknameText.visibility = View.VISIBLE }
- アプリをビルドして実行します。見た目と動作は以前とまったく同じです。
Tip: 変更後にコンパイラエラーが表示される場合は、Build > Clean Project を選択してから、Build > Rebuild Project を選択します。これを行うと、通常、生成されたファイルが更新されます。それ以外の場合は、File > Invalidate Caches/Restart を選択して、より完全なクリーンアップを行います。
Tip:
これまでに、アプリ内のすべてのリソースへの参照を保持する Resources オブジェクトについて学びました。同様に、view を参照する時に Binding
オブジェクトを考えることができます。ただし、Binding
オブジェクトははるかに洗練されています。
データバインディングを使用してデータを表示する
データバインディングを利用して、データクラスを View で直接利用できるようにすることができます。この手法はコードを単純化し、より複雑なケースを処理するために非常に価値があります。
この例では、string リソースを使用して名前とニックネームを設定する代わりに、名前とニックネームのデータクラスを作成します。データバインディングを使用して、データクラスを View で使用できるようにします。
MyName データクラスを作成する
Android Studio の
java
ディレクトリで、MyName.kt
ファイルを開きます。このファイルがない場合は、新しい Kotlin ファイルを作成して、MyName.kt
という名前をつけます。名前とニックネームのデータクラスを定義します。デフォルト値として空の文字列を使用します。
data class MyName(var name: String = "", var nickname: String = "")
レイアウトにデータを追加する
activity_main.xml
ファイルでは、名前は現在、string resource の値が TextView
に設定されています。名前への参照をデータクラスのデータへの参照に置き換える必要があります。
activity_main.xml
を開いて Code タブに切り替えます。レイアウト上部の
<layout>
タグと<LinearLayout>
タグの間に、<data></data>
タグを挿入します。これは、View と Data を接続する場所です。
<data> </data>
データタグ内で、クラスへの参照を保持する名前付き変数を宣言できます。
<data>
タグ内に<variable>
タグを追加します。name
パラメータを追加して、変数に "myName" という名前をつけます。Type
パラメータを追加し、型をMyName
データクラスの完全修飾名(package name + variable name) に設定します。
<variable name="myName" type="com.example.android.aboutme.MyName" />
これで、名前に string resource を使用する代わりに、myName
変数を参照できます。
Note:
使用している Android Studio のバージョンによっては、パッケージ名が異なる場合があります。アプリのパッケージ名が type
変数のパーケージ名と一致していることを確認してください。
android:text="@string/name"
を以下のコードに置き換えます。
@={}
は、中括弧内で参照されるデータを取得するための directive です。
myName
は、以前に定義した myName
変数を参照します。これは、myName
データクラスを指し、クラスから name
プロパティをフェッチします。
android:text="@={myName.name}"
データを作成する
これで、レイアウトファイルのデータへの参照ができました。次に、実際のデータを作成します。
MainActivity.kt
ファイルを開きます。onCreate()
の上で、慣例により、myName
と呼ばる private 変数を作成します。変数にMyName
データクラスのインスタンスを割り当て、名前を渡します。
private val myName: MyName = MyName("Aleks Haecky")
onCreate()
で、レイアウトファイルのmyName
変数の値を、宣言したmyName
変数の値に設定します。XML の変数に直接アクセスすることはできません。バインディングオブジェクトを介してアクセスする必要があります。
binding.myName = myName
- 変更後にバインディングオブジェクトを更新する必要があるため、エラーが表示される場合があります。アプリをビルドすれば、エラーはなくなるはずです。
TextView のニックネームにデータクラスを使用する
最後のステップは、ニックネームの TextView
にもデータクラスを使用することです。
activity_main.xml
を開きます。nickname_text
text view で、text
プロパティを追加します。以下に示すように、データクラスでnickname
を参照します。
android:text="@={myName.nickname}"
MainActivity
で、nicknameText.text = nicknameEdit.text.toString()
をmyName
変数にニックネームを設定するコードに置き換えます。
myName?.nickname = nicknameEdit.text.toString()
ニックネームを設定したら、コードで UI を新しいデータで更新します。これを行うには、すべてのバインディング式を無効にして、新しいデータで再作成されるようにする必要があります。
- ニックネームを設定した後に
invalidateAll()
を追加して、更新されたバインディングオブジェクトの値で UI が更新されるようにします。
binding.apply { myName?.nickname = nicknameEdit.text.toString() invalidateAll() ... }
- アプリをビルドして実行すると、以前とまったく同じように機能するはずです。
まとめ
データバインディングを使用して findViewById()
の呼び出しを置き換える手順:
build.gradle
ファイルの android セクションでデータバインディングを有効にします。buildFeatures { dataBinding true }
<layout>
を XML レイアウトのルート View として使用します。バインディング変数を定義します。
private lateinit var binding: ActivityMainBinding
MainActivity
でバインディングオブジェクトを作成し、setContentView
を置き換えます。binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
findViewById()
への呼び出しを、バインディングオブジェクトの View への参照に置き換えます。例:findViewById<Button>(R.id.done_button)
=>binding.doneButton
(この例では、View の名前は、XML の View のid
のキャメルケース表記で生成されます。)
View をデータにバインドする手順:
データのデータクラスを作成します。
<layout>
タグ内に<data>
ブロックを追加します。name と データクラスである方のタイプを使用して、
<variable>
を定義します。
<data> <variable name="myName" type="com.example.android.aboutme.MyName" /> </data>
MainActivity
で、データクラスのインスタンスを使用して変数を作成します。例:private val myName: MyName = MyName("Yamato Otaka)
バインディングオブジェクトの変数を作成した変数に設定します。
binding.myName = myName
XML で、View のコンテンツを
<data>
ブロックで定義した変数に設定します。ドット表記を使用して、データクラス内のデータにアクセスします。android:text="@={myName.name}"