View BindingでViewの参照を容易に!

投稿日:  更新日:

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の参照を可能にします。

View Binding有効View Binding無効
        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」を選んでください。

Viewシステムの選択

スポンサーリンク

Bindingの動作

View Bindingの有効化が行われると、Android Studioはレイアウトファイルを認識(変更・新規作成)する際に、ViewBindingクラス(例:ActivityMainBinding)を自動生成します。

View Bindingのフロー

ViewBindingクラスの作成先フォルダ

ViewBindingクラスのファイル名は、レイアウトファイル名(例:activity_main.xml)を図のように変換したものになります。

ViewBindingクラスのネーミングルール

ViewBinding#infulate()を実行すれば、プロパティへ子Viewの参照が登録されるので、ViewBindingのインスタンスから取り出せます。

プロパティ名は子Viewの「リソースID」になっています。ただし、トップViewは「root(固定の名前)」になっています。

        val _binding = ActivityMainBinding.inflate(layoutInflater)
        val _root = _binding.txtMesg        // レイアウトファイルのトップView
        val _txtMesg = _binding.txtMesg     // TextView
		

ViewBindingのView参照

スポンサーリンク

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の実装」を参照

スポンサーリンク

関連記事:

投稿が見つかりませんでした。
スポンサーリンク