View BindingはViewシステムを用いている場合に、プログラムから子Viewの参照を容易にする仕組みです。
Android(OS:オペレーションシステム)ではなく、Android Studio(IDE:開発環境)の機能です。プログラミングの補助を目的とします。
Googleは「利点がある」と述べています。しかし、私は利点に感じないので、積極的な利用をしていません。
今まで、単なるオプションと考えていたのですが、最近は一般的になりつつあり、世に出回っているサンプルで頻繁に見かけるようになりました。
ですので、ここに備忘録として、まとめます。
※環境:Android Studio Ladybug | 2024.2.1
Kotlin 1.9.24
(Viewシステムのプロジェクトは1.9.24が選ばれる)
(Composeのプロジェクトは2.0.0が選ばれる)
目次
View Bindingとは
View BindingはViewシステム(XXX.xmlで画面構成を定義する環境)を用いている場合に、プログラムから子View(ButtonやtextViewなど)の参照を容易にする仕組みです。
Android(OS:オペレーションシステム)ではなく、Android Studio(IDE:開発環境)の機能です。プログラミングの補助を目的とします。
例えば、以下のようなレイアウトファイル(activity_main.xml)があったとします。
... <TextView android:id="@+id/txtMesg" .../> ...
View Bindingでは、レイアウトファイルからViewBinderクラス(ActivityMainBinding)を自動生成し、このクラスを使ってViewの参照を可能にします。
val _binding = ActivityMainBinding.inflate(layoutInflater) val _txtMesg = _binding.txtMesg // TextViewの参照
val _txtMesg = findViewById<TextView>(R.id.txtMesg)
View Bindingの利点は次のように述べられています。※[ ]内は私的な感想
- [△]子Viewの参照を簡単に書ける
- [?]null安全性:無効なIDのために例外の発生するリスクが無い
- [?]型安全性:無理に異なる型(View)へ変換するミスが無い
問題点は初回で修正すれば良く、以後に発生しないのであれば「利点と言えるか?」が疑問です。
ですので、私はView Bindingを積極的に利用していません。それに、Jetpack Composeを使っていれば無関係です。
環境設定
View Bindingを利用するための環境設定です。
View Bindingの有効化
View Bindingはモジュール毎に有効・無効を指定します。
有効にするために、「app/build.gradle」へ次の設定を行います。
android { ... buildFeatures { viewBinding = true } ... }
※最新の「Android Studio Ladybug」から、デフォルトでView Bindingが有効になっています。それ以前は上記の設定が必要です。
Viewシステムのプロジェクト作成
View BindingはViewシステムを用いている場合の機能です。
現状はJetpack Composeによる開発が主流になっているので、Viewシステムを意図的に選択しなければなりません。
新規プロジェクトの作成時に、「Views Activity」を選んでください。
Bindingの動作
View Bindingの有効化が行われると、Android Studioはレイアウトファイルを認識(変更・新規作成)する際に、ViewBindingクラス(例:ActivityMainBinding)を自動生成します。
ViewBindingクラスのファイル名は、レイアウトファイル名(例:activity_main.xml)を図のように変換したものになります。
ViewBinding#infulate()を実行すれば、プロパティへ子Viewの参照が登録されるので、ViewBindingのインスタンスから取り出せます。
プロパティ名は子Viewの「リソースID」になっています。ただし、トップViewは「root(固定の名前)」になっています。
val _binding = ActivityMainBinding.inflate(layoutInflater) val _root = _binding.txtMesg // レイアウトファイルのトップView val _txtMesg = _binding.txtMesg // TextView
Activityの例
Activity#setContentViewは引数にViewを採れるので、ActivityMainBinding#rootを入力しています。
この例はonCreate内でActivityMainBindingの利用が完結していますが、他の状態(onStartやonResumeなど)で利用したければ、Activityクラス内にBindingインスタンスを保持する必要があります。後述する「Fragmentの例」が参考になります。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" ...> <LinearLayout android:id="@+id/layMesgPanel" ...> <TextView android:id="@+id/txtMesg" ... /> <LinearLayout android:id="@+id/layBtnPanel" ...> <Button android:id="@+id/btnRed" ... /> <Button android:id="@+id/btnBlue" ... /> </LinearLayout> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() val _binding = ActivityMainBinding.inflate(layoutInflater) setContentView(_binding.root) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.sample1)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } val _btnRed = _binding.btnRed val _btnBlue = _binding.btnBlue val _txtMesg = _binding.txtMesg _btnRed.setOnClickListener { _txtMesg.setTextColor(Color.RED) } _btnBlue.setOnClickListener { _txtMesg.setTextColor(Color.BLUE) } } }
Fragmentの例
Fragment#onViewCreatedの引数viewはFragmentのトップViewが渡されてきます。
しかし、子Viewへのアクセスに、この引数viewは使用しません。
Fragmentクラス内に保持したBindingインスタンスを用いて、子Viewへアクセスを行います。
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MesgPanelFragment"> <LinearLayout android:id="@+id/layMesgPanel" ...> <TextView android:id="@+id/txtMesg" ... /> <LinearLayout android:id="@+id/layBtnPanel" ...> <Button android:id="@+id/btnRed" ... /> <Button android:id="@+id/btnBlue" ... /> </LinearLayout> </LinearLayout> </FrameLayout>
class MesgPanelFragment : Fragment() { private var _binding: FragmentMesgPanelBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentMesgPanelBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val _btnRed = binding.btnRed val _btnBlue = binding.btnBlue val _txtMesg = binding.txtMesg _btnRed.setOnClickListener { _txtMesg.setTextColor(Color.RED) } _btnBlue.setOnClickListener { _txtMesg.setTextColor(Color.BLUE) } } override fun onDestroyView() { super.onDestroyView() _binding = null } }
RecyclerViewの例
ViewHolderクラスにおいて、引数をアイテムのトップViewからBindingへ変更すれば良いです。
このViewHolderとBindingは子Viewの参照を持つという点で、役割が重複しています。どちらかに集約したいですが、方法はありません。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="38dp" android:layout_margin="1dp" android:background="#DFE6F6"> <TextView android:id="@+id/txtMesg" ... /> </androidx.constraintlayout.widget.ConstraintLayout>
class ArrayAdapter(var items: Array<String>) : RecyclerView.Adapter<ArrayAdapter.ArrayViewHolder>() { // ----- ビューホルダー ------------------------------------------ class ArrayViewHolder(val binding: SimpleItemBinding) : RecyclerView.ViewHolder(binding.root) { val mesg = binding.txtMesg } // ----- アダプター本体 ------------------------------------------ override fun getItemCount(): Int { return items.size // アイテム数を返す } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ArrayViewHolder { val _binding = SimpleItemBinding.inflate( LayoutInflater.from( parent.context), parent, false ) return ArrayViewHolder(_binding) // ViewHolderを返す } override fun onBindViewHolder(holder: ArrayViewHolder, position: Int) { holder.mesg.text = items[position] // 要素とデータを紐づける } }
※その他、RecyclerView関連の記述は「RecyclerViewの実装」を参照
関連記事: