ビュー バインディング

ビュー バインディングとは、ビューを操作するコードを簡単に記述できる機能です。モジュール内でビュー バインディングを有効にすると、そのモジュール内に存在する XML レイアウト ファイルごとにバインディング クラスが生成されます。バインディング クラスのインスタンスには、対象レイアウト内で ID を持つすべてのビューへの直接参照が組み込まれます。

ほとんどの場合、ビュー バインディングは findViewById の後継となります。

セットアップ手順

ビュー バインディングは、モジュール単位で有効化します。モジュール内でビュー バインディングを有効にするには、次の例に示すように、build.gradle ファイルに viewBinding 要素を追加します。

android {
        ...
        viewBinding {
            enabled = true
        }
    }
    

バインディング クラスの生成中にレイアウト ファイルを無視する場合、そのレイアウト ファイルのルートビューに tools:viewBindingIgnore="true" 属性を追加します。

<LinearLayout
            ...
            tools:viewBindingIgnore="true" >
        ...
    </LinearLayout>
    

使用方法

モジュールに対してビュー バインディングを有効にすると、そのモジュールに含まれる XML レイアウト ファイルごとにバインディング クラスが生成されます。各バインディング クラスには、ルートビューへの参照と、ID を持つすべてのビューへの参照が組み込まれます。バインディング クラスの名前は、XML ファイルの名前をキャメルケースに変換して、末尾に「Binding」という単語を追加することで生成されます。

たとえば、result_profile.xml というレイアウト ファイルがあるとします。

<LinearLayout ... >
        <TextView android:id="@+id/name" />
        <ImageView android:cropToPadding="true" />
        <Button android:id="@+id/button"
            android:background="@drawable/rounded_button" />
    </LinearLayout>
    

生成されるバインディング クラスは、ResultProfileBinding という名前になります。このクラスには、2 つのフィールドがあります。name という名前の TextView フィールドと、button という名前の Button フィールドです。レイアウト内の ImageView には ID がないため、バインディング クラス内にそのビューへの参照は存在しません。

各バインディング クラスには getRoot() メソッドも含まれ、対象レイアウト ファイルのルートビューを直接参照します。上記の例の場合、ResultProfileBinding クラスの getRoot() メソッドは、LinearLayout ルートビューを返します。

以下のセクションでは、アクティビティとフラグメントでの、生成されたバインディング クラスの使用について説明します。

アクティビティでビュー バインディングを使用する

アクティビティで使用するバインディング クラスのインスタンスをセットアップするには、アクティビティの onCreate() メソッドで次の手順を実施します。

  1. 生成されたバインディング クラスに含まれる静的 inflate() メソッドを呼び出します。これにより、アクティビティで使用するバインディング クラスのインスタンスが作成されます。
  2. getRoot() メソッドを呼び出すか、Kotlin プロパティ構文を使用して、ルートビューへの参照を取得します。
  3. ルートビューを setContentView() に渡して、画面上のアクティブ ビューにします。

Kotlin

    private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }
    

Java

    private ResultProfileBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ResultProfileBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
    }
    

これで、バインディング クラスのインスタンスを使用して任意のビューを参照できるようになりました。

Kotlin

    binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }
    

Java

    binding.getName().setText(viewModel.getName());
    binding.button.setOnClickListener(new View.OnClickListener() {
        viewModel.userClicked()
    });
    

フラグメントでビュー バインディングを使用する

フラグメントで使用するバインディング クラスのインスタンスを設定するには、フラグメントの onCreateView() メソッドで次の手順を実施します。

  1. 生成されたバインディング クラスに含まれる静的 inflate() メソッドを呼び出します。これにより、フラグメントが使用するバインディング クラスのインスタンスが作成されます。
  2. getRoot() メソッドを呼び出すか、Kotlin プロパティ構文を使用して、ルートビューへの参照を取得します。
  3. onCreateView() メソッドからルートビューを返して、画面上のアクティブ ビューにします。

Kotlin

    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
    

Java

    private ResultProfileBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ResultProfileBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
    

これで、バインディング クラスのインスタンスを使用して任意のビューを参照できるようになりました。

Kotlin

    binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }
    

Java

    binding.getName().setText(viewModel.getName());
    binding.button.setOnClickListener(new View.OnClickListener() {
        viewModel.userClicked()
    });
    

findViewById との違い

ビュー バインディングには、findViewById を使用するよりも大きなメリットがあります。

  • null の安全性: ビュー バインディングは、ビューへの直接参照を作成するため、無効なビュー ID によって NullPointerException が発生することはありません。また、レイアウトの一部の構成内だけに存在するビューがあった場合、バインディング クラス内でそのビューへの参照を含むフィールドには @Nullable のマークが付きます。
  • 型の安全性: 各バインディング クラス内のフィールドは、XML ファイル内で参照しているビューと合致する型を持ちます。そのため、ClassCastException のリスクがありません。

このような違いがあるため、レイアウトとコードとの間に互換性がない場合は、実行時ではなくコンパイル時にビルドが失敗することになります。

データ バインディングとの比較

ビュー バインディングと データ バインディングは、両方ともビューを直接参照できるバインディング クラスを生成します。ただし、ビュー バインディングはよりシンプルなユースケースを扱うことを目的としており、データ バインディングに比べて次のようなメリットがあります。

  • コンパイルが高速: ビュー バインディングはアノテーション処理を必要としないため、コンパイル時間が短縮されます。
  • 使いやすい: ビュー バインディングは特別にタグ付けされた XML レイアウト ファイルを必要としないため、より迅速にアプリで利用できます。モジュールでビュー バインディングを有効にすると、そのモジュールのすべてのレイアウトに自動的に適用されます。

逆に、ビュー バインディングには、データ バインディングに比べて次のような制約があります。

これらの内容をふまえると、プロジェクトでビュー バインディングとデータ バインディングの両方を使用することが最適な場合があります。高度な機能を必要とするレイアウトではデータ バインディングを使用でき、そうでないレイアウトではビュー バインディングを使用できます。