ビュー バインディング Android Jetpack の一部

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

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

設定

ビュー バインディングは、モジュール単位で有効化します。モジュールでビュー バインディングを有効にするには、次の例に示すように、モジュール レベルの build.gradle ファイルで viewBinding ビルド オプションを true に設定します。

Groovy

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

Kotlin

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

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

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

使用方法

モジュールに対してビュー バインディングを有効にすると、そのモジュールに含まれる XML レイアウト ファイルごとにバインディング クラスが生成されます。各バインディング クラスには、ルートビューへの参照と、ID を持つすべてのビューへの参照が組み込まれます。バインディング クラスの名前は、XML ファイルの名前を Pascal ケースに変換して、末尾に「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.name.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.name.setText(viewModel.getName());
binding.button.setOnClickListener(new View.OnClickListener() {
    viewModel.userClicked()
});

さまざまな構成に関するヒントを提供する

複数の構成にわたってビューを宣言する場合は、特定のレイアウトに応じて異なるビュータイプを使用するのが適切な場合があります。次のコード スニペットは、その例を示しています。

# in res/layout/example.xml

<TextView android:id="@+id/user_bio" />

# in res/layout-land/example.xml

<EditText android:id="@+id/user_bio" />

この場合、TextView が共通の基底クラスであるため、生成されたクラスは TextView 型のフィールド userBio を公開すると予想されます。技術的な制限により、ビュー バインディング コード生成ツールはこれを判断できず、代わりに View フィールドを生成します。この場合、後で binding.userBio as TextView を使用してフィールドをキャストする必要があります。

この制限を回避するため、ビュー バインディングは tools:viewBindingType 属性をサポートしており、生成されたコードで使用する型をコンパイラに指示できます。上の例では、この属性を使用して、コンパイラがフィールドを TextView として生成するようにします。

# in res/layout/example.xml (unchanged)

<TextView android:id="@+id/user_bio" />

# in res/layout-land/example.xml

<EditText android:id="@+id/user_bio" tools:viewBindingType="TextView" />

別の例として、BottomNavigationView を含むレイアウトと NavigationRailView を含むレイアウトの 2 つがあるとします。どちらのクラスも NavigationBarView を拡張します。このクラスには実装の詳細のほとんどが含まれています。現在のレイアウトに存在するサブクラスをコードで正確に把握する必要がない場合は、tools:viewBindingType を使用して、両方のレイアウトで生成された型を NavigationBarView に設定できます。

# in res/layout/navigation_example.xml

<BottomNavigationView android:id="@+id/navigation" tools:viewBindingType="NavigationBarView" />

# in res/layout-w720/navigation_example.xml

<NavigationRailView android:id="@+id/navigation" tools:viewBindingType="NavigationBarView" />

ビュー バインディングは、コード生成時にこの属性の値を検証できません。コンパイル時エラーと実行時エラーを回避するには、値が次の条件を満たしている必要があります。

  • 値は android.view.View から継承するクラスである必要があります。
  • 値は、配置先のタグのスーパークラスである必要があります。たとえば、次の値は機能しません。

      <TextView tools:viewBindingType="ImageView" /> <!-- ImageView is not related to TextView. -->
      <TextView tools:viewBindingType="Button" /> <!-- Button is not a superclass of TextView. -->
    
  • 最終的な型は、すべての構成で一貫して解決する必要があります。

findViewById との違い

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

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

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

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

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

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

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

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

参考情報

ビュー バインディングの詳細については、以下の参考情報をご確認ください。

ブログ

動画