ビュー バインディング 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 ファイルの名前をパスカルケースに変換し、末尾に「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 という名前になります。このクラスには、name という名前の TextViewbutton という Button という 2 つのフィールドがあります。レイアウト内の ImageView には ID がないため、バインディング クラス内でその 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 型のフィールド userBio を公開することが想定されます。これは、TextView が共通の基本クラスであるためです。技術的な制限により、ビュー バインディングのコード生成ツールはこれを特定できず、代わりに 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 ファイルで参照するビューと一致する型があります。つまり、クラスキャスト例外のリスクはありません。

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

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

ビュー バインディングとデータ バインディングはどちらも、ビューを直接参照するために使用できるバインディング クラスを生成します。ただし、ビュー バインディングはより単純なユースケースを扱うことを想定しており、データ バインディングと比べて次のようなメリットがあります。

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

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

以上のことから、1 つのプロジェクトでビュー バインディングとデータ バインディングの両方を使用するほうがよい場合もあります。高度な機能が必要なレイアウトではデータ バインディングを、必要のないレイアウトではビュー バインディングを使用できます。

参考情報

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

サンプル

ブログ

動画