blog

DataBinding、フレームワークの好き嫌いは分かれる!

私が勉強を始めたころは、フレームワークのアーキテクチャをあまり意識しておらず、これとこれとがどうなっているのか、なぜこんなに面倒なのか?だから私はそれを使用したことはありません。 その後、私は本当にM...

Mar 17, 2020 · 22 min. read
シェア

簡単

DataBindingを学び始めた当初は、フレームワークのアーキテクチャをあまり意識しておらず、学んでから「何と何が一緒で、なんでそんな面倒なことしなきゃいけないの?ということで使っていません。

その後、MVVMフレームワークと組み合わせて、その使い方を本当に理解した後、私はそれを使用することは本当に多くの作業を節約することがわかりました、それは一定の閾値があるため、嫌われ、エラープロンプトは十分に親切ではありません。

この記事では、DataBindingの使用方法とMVVMフレームワークでの使用方法について説明します。

DataBinding簡単に言えば、それはあなたがデータとビューをバインドすることができますデータバインディングフレームワークです、データが変更されると、ビューが自動的に変更され、ビューが変更されると、データもそれに応じて変更されます。原理は簡単ですが、実際には、対応するコードのバインディングを生成するためのツールであり、ライフサイクルのセキュリティ、メモリリークはありません。

それはエラープロンプトの正常な動作の一種である場合ASは、いくつかの奇妙なエラーを促すかもしれませんが、そのようなBRクラスは赤、赤プロンプト内のxmlなどを報告したように、無視することができますが、あまりにも多くはない、一度エラープロンプトが離れて怖がってはいけません。

、MVVMフレームワークによるDataBindingのカプセル化

class MainActivity : DataBindingBaseActivity<ActivityMainBinding, MainViewModel>(
 R.layout.activity_main, BR.viewModel
) {
 override fun initParam() {
 }
 override fun initViewObservable() {
 }
 override fun initData() {
 super.initData()
 }
}

対応するMainViewModelは以下の通りです:

class MainViewModel(app: Application) : BaseViewModel<BaseModel>(app) {
}

対応するxmlは以下の通り:

<layout xmlns:android="http://..///id"
 xmlns:app="http://..//-to"
 xmlns:tools="http://../ls"
 >
 <data>
 <!--
 変数を宣言する。通常、vmを持つインターフェースでは、対応するvm変数を宣言する必要がある。
 インターフェイスを構築するときに、変数を BaseXXXX
 -->
 <variable
 name="viewModel"
 type="com.imyyq.sample.MainViewModel"
 />
 </data>
 <LinearLayout
 android:id="@+id/linear"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity"
 >
 </LinearLayout>
</layout>

協会は以下の通り:

こうすることで、xml は ViewModel クラスにバインドされ、xml はインターフェイスとして操作できるようになります。

VMに複数のMainViewModelがある場合は、以下のように手動で追加する必要があります:

class MainActivity : DataBindingBaseActivity<ActivityMainBinding, MainViewModel>(
 R.layout.activity_main, BR.viewModel
) {
 // メインのvmの他に、次のようなvmがある。 fragment-ktx viewModelsエクステンションを使えば、その場で1行のコードでインスタンスを作成できる。
 private val mTestViewModel by viewModels<TestViewModel>()
 override fun initData() {
 super.initData()
 //  
 mBinding.setVariable(BR.testViewModel, mTestViewModel)
 // あるいは、そう思える:
 // mBinding.testViewModel = mTestViewModel
 }
}

xmlは以下の通り:

<layout xmlns:android="http://..///id"
 xmlns:app="http://..//-to"
 xmlns:tools="http://../ls"
 >
 <data>
 <variable
 name="viewModel"
 type="com.imyyq.sample.MainViewModel"
 />
 <variable
 name="testViewModel"
 type="com.imyyq.sample.TestViewModel"
 />
 </data>
 ....
</layout>

上記はVとVMを組み合わせたものですが、なぜMVVMフレームワークと呼ばれるのでしょうか?MはModelデータレイヤー、VはViewビューレイヤー、VMはViewModelビジネスロジックレイヤーだからです。

データバインディング以前の世界

DataBindingを使う前は、例えば、時間のかかる処理を実行し、その結果をインターフェイスに表示する、というようなコードだったかもしれません。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity"
 >
 <TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 />
</LinearLayout>
class MainActivity : ViewBindingBaseActivity<ActivityMainBinding, MainViewModel>() {
 override fun initViewObservable() {
 //   LiveData
 mViewModel.mMutableLiveData.observe(this, Observer {
 mBinding.tvMsg.text = it
 })
 }
 override fun initData() {
 super.initData()
 // データのロードを開始する
 mViewModel.loadData()
 }
 // バインディングのインスタンスを提供するには、このメソッドをオーバーライドする必要がある。
 オーバーライド fun initBinding(inflater: LayoutInflater, container: ViewGroup?) =
 ActivityMainBinding.inflate(inflater)
}
class MainViewModel(app: Application) : BaseViewModel<BaseModel>(app) {
 val mMutableLiveData = MutableLiveData<String>()
 fun loadData() {
 // ロードダイアログを表示する
 ロードダイアログを表示する
 //  
 launchUI {
 // 時間のかかる作業をシミュレートする
 遅延
 //  
 mMutableLiveData.value = "その結果が私だ。"
 // ダイアログを隠す
 dismissLoadingDialog()
 }
 }
}

これは、VMがアクティビティに結果を表示できるように、LiveDataをリッスンすることで行われます。

データバインディングの世界

レイアウトをレイアウトタグで囲むことから始めます。

<layout xmlns:android="http://..///id"
 xmlns:app="http://..//-to"
 xmlns:tools="http://../ls"
 >
 <data>
 <variable
 name="viewModel"
 type="com.imyyq.sample.MainViewModel"
 />
 </data>
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity"
 >
 <!-- 次に、ビューの属性テキストをバインドする変数を指定する。 -->
 <TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{viewModel.MMutableLiveData}"
 />
 </LinearLayout>
</layout>

DataBindingのバインディングはすべてandroid:text="@{viewModel.mMutableLiveData}"のような @{}構造です。このようにすると、textプロパティはviewModel.mMutableLiveData変数にバインドされ、mMutableLiveDataが変更されるとtextも自動的に変更されます。変数が mMutableLiveData のように先頭に小文字が一つしかない場合、DataBinding は自動的にその文字を大文字にするので、xml で使用する場合は viewModel.mMutableLiveData になりますが、mmMutableLiveData のように小文字が二つ以上ある場合はそうはなりません。変数が mmMutableLiveData で小文字が 2 文字以上ある場合、最初の文字は大文字になりません。

class MainActivity : DataBindingBaseActivity<ActivityMainBinding, MainViewModel>(
 R.layout.activity_main, BR.viewModel
) {
 override fun initData() {
 super.initData()
 // データのロードを開始する
 mViewModel.loadData()
 }
}

これにより、手動で観察したり、手動で setText メソッドを呼び出したりする必要がなくなります。

DataBindingはAS 3.1以来、LiveDataをサポートしています。LiveDataに応答するためには、もう1つのステップが必要です:

mBinding.lifecycleOwner = this

これは、LiveDataに正しく応答する唯一の方法であり、フレームワークがすでにあなたのためにこれを行います。

多分、あなたは、多くのコードを保存しないように、ああ、ちょうど観察を保存するので、確かに多くを保存しないように見えるが、DataBindingの機能はそれ以上である、と言うでしょう。

、データバインディング使用例集

上記のように、xmlでビューのidを定義した後、対応する変数がバインディングクラスで自動的に生成されます。

MVVMArchitectureフレームワークは、組み込みのDataBindingUtilツールキットを使用して、インスタンスを作成するのに役立ちます。ビューとバインドすることができます。これは以下のようになります:

以下は、DataBindingのその他の使用法のリストです。

輸入可能 import

<data>
 <import type="android.view.View"/>
 <!-- 名前の衝突、別名の使用 -->
 <import
 alias="MyView"
 type="com.example.imyyq.test.View"
 />
</data>

インポートの用途は?後述するView.GONEやString.valueOfなど、クラスのプロパティやメソッドを使うことができます。

変数の数は1つ以上です。

<data>
 <import type="java.util.List"/>
 <!-- variableそれはいくつでも可能だ。 -->
 <!-- インポートされた型。<>などとエスケープする必要がある。 -->
 <variable
 name="userList"
 type="List<User>"
 />
 <variable
 name="user"
 type="com.imyyq.databindingtest.User"
 />
</data>

変数は間違いなく複数回定義できますし、一般的なスパイクはエスケープする必要があります。

自動生成されたクラス名のカスタマイズ

デフォルトのバインディングクラス生成ルールは次のとおりです:xml名からアンダースコアを削除し、最初の文字を大文字にし、最後にactivity_mainのようにバインディングを追加すると、クラスの自動生成は次のようになります。次のようにします:

<data class="HeiHei">
</data>

includeタグのサポート

<layout xmlns:android="http://..///id"
 xmlns:app="http://..//-to"
 xmlns:tools="http://../ls"
 >
 <data>
 <variable
 name="viewModel"
 type="com.imyyq.sample.MainViewModel"
 />
 </data>
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity"
 >
 <TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{viewModel.MMutableLiveData}"
 />
 <!-- レイアウトをインクルードし、テスト変数をバインドする -->
 <include
 layout="@layout/layout_test"
 app:test="@{viewModel.MMutableLiveData}"
 />
 </LinearLayout>
</layout>

ここでlayout_testは以下の通り:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://..///id">
 <data>
 <variable
 name="test"
 type="String"
 />
 </data>
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 >
 <TextView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{test}"
 />
 </LinearLayout>
</layout>

ViewStubタグのサポート

レイアウトは以下の通り:

<layout xmlns:android="http://..///id">
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="horizontal"
 >
 <Button
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:onClick="inflateViewStub"
 android:text="Inflate the ViewStub"
 />
 <ViewStub
 android:id="@+id/view_stub"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout="@layout/view_stub"
 />
 </LinearLayout>
</layout>

view_stub.xml を次のようにします:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://..///id">
 <data>
 <variable
 name="user"
 type="com.xxx.databinding.model.User"
 />
 </data>
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="horizontal"
 >
 <TextView
 android:id="@+id/firstName"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@{user.firstName}"
 />
 <TextView
 android:id="@+id/lastName"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@{user.lastName}"
 />
 </LinearLayout>
</layout>

コードは以下の通り:

// インフレート・コールバックを設定する
mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener()
{
 @Override
 public void onInflate(ViewStub stub, View view)
 {
 // バインディング・インスタンスを取得する
 ViewStubBinding binding = DataBindingUtil.bind(view);
 User user = new User("liang", "fei");
 binding.setUser(user);
 }
});
// isInflated これはViewStubのメソッドではなく、DataBindingによって自動的に生成されるメソッドである。これは
もし!mBinding.viewStub.isInflated())
{
 mBinding.viewStub.getViewStub().inflate();
}

これは特に、ViewStubが膨張しているかどうかを判断するのに便利です。

双方向バインディングのサポート

例えば、LiveDataにバインドされているEditTextは、EditTextの内容が変更されると、LiveDataの内容も変更されます。これは次のようになります:

<EditText
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@={viewModel.editText}"
 />

ViewModelはViewインスタンスを保持することができないので、DataBindingがなければ、Viewを介してViewModelにEditTextを渡すしかありません。DataBindingの方がはるかに便利です。

双方向バインディングをサポートする組み込みプロパティは以下のとおりです:

もちろん、カスタム2ウェイバインディングも用意されています。

バインディングメソッドのサポート

上記の操作はすべてバインドされた変数ですが、メソッドをバインドすることはできますか?もちろんできます。onClickイベントを例にしてみましょう。

val onClick = View.OnClickListener {
 Log.i("MainViewModel", "commonLog - onClick:  ")
}
fun onClick(v: View) {
 Log.i("MainViewModel", "commonLog - onClick:  ")
}
fun onClick() {
 Log.i("MainViewModel", "commonLog - onClick: パラメータなし、またはパラメータありでも構わないが、xmlで値を渡さなければならない。")
}
<!-- 1バインド変数 -->
<Button
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:onClick="@{viewModel.onClick}"
/>
<!-- 2結束方法 -->
<Button
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:onClick="@{viewModel::onClick}"
/>
<!-- 3エクスプレッションライティング -->
<Button
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:onClick="@{() -> viewModel.onClick()}"
/>

バインディング・メソッドのシグネチャは、プロパティが必要とするオブジェクト・メソッドのシグネチャと同じでなければなりません。この場合、OnClickListenerのonClickメソッドです。同様に、onLongClickリスナーであれば、Bool値を返すメソッドが必要です。

3つ目は式の書き方で、これについては後述しますが、Java8を勉強した人なら誰でも、矢印 -> やダブルコロン :: がラムダ式であることを知っています。

表現のサポート

DataBindingはいくつかの式をサポートし、また、クリックイベントなどのラムダ式をサポートしていますが、DataBindingは、ユニットテストをサポートしていないため、あまり複雑にしないことをお勧めします、エラーレポートは非常にインテリジェントではありません。サポートされている演算子とキーワードは次のとおりです:

this""、super、new、明示的なジェネリック呼び出しはありません。例を以下に示します:

a:
    text = '@{String.valueOf(index\x20+\x201)}';
c:
    visibility = '@{age\x20>\x2013\x20?\x20View.GONE\x20:\x20View.VISIBLE}';
obj:
    transitionName = '@{\x22image_\x22\x20+\x20id}';
b:
    transitionName = '@{`image_`\x20+\x20id}';

Stringはjava.langパッケージなのでインポートする必要はありませんが、Viewはインポートする必要があります。

インライン文字列が必要な場合は、シングルクォートで囲む必要があります。または、上記のように `` 記号で囲んでください。

二重疑問符、三項演算子の簡略化

bar:
    text = '@{user.displayName\x20??\x20user.lastName}';

displayNameがNULLでない場合はdisplayNameが使用され、そうでない場合はlastNameが使用されます。

コレクションや配列などの要素へのアクセス

配列にアクセスする場合と同様に、[]括弧を使ってアクセスします:

a:
    text = '@{list[index]}';
foo:
    text = '@{map[key]}';

マップ型であれば、プロパティにアクセスするのと同じように、map.keyとすることもできます。また、自動的に強変換されるので、値が文字型でない場合、DataBindingはtext属性にマッチする型に強変換します。

リソースへのアクセス

例えば、文字列、寸法、色など。

c:
    padding = '@{large?\x20@dimen/largePadding\x20:\x20@dimen/smallPadding}';
foo:
    text = '@{@string/nameFormat(firstName,\x20lastName)}';
b:
    text = '@{@plurals/banana(bananaCount)}';

それはカスタムビューの場合は、メソッドのパラメータを設定するようなDrawableを必要とし、xmlのDrawableオブジェクトを設定することはできません、唯一のint型リソースのアドレスは、コンバータをカスタマイズする必要があります以下のコンバータをすることができます。

ヌルポインタ例外の回避

次のようにバインドします:

android:text="@{String.valueOf(user.age)}"

userがNULLの場合、NULLポインタ例外は発生しません。 DataBindingは変数の型に基づいてデフォルト値を自動的に設定します。例えば、ageがintの場合、フィールドだけでなくメソッドでもデフォルト値は0です。

他のビューの値の使用

<TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{viewModel.mmMutableLiveData}"
 />
<EditText
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{tvMsg.text}"
 />

tv_msgは自動的にtvMsgフィールドを生成するので、関連するメソッドを直接呼び出すことができます。

プレビューウィンドウにビューのデフォルト値を表示します。

プレビューウィンドウのビューではなく、実際のランタイムビューは、多くの場合、TextViewなどのプレビューウィンドウでビューをプレビューする必要があることに注意してください、設定するには2つの方法があります。次のように:

<TextView
 xmlns:tools="http://../ls"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{viewModel.text, default= }"
 tools:text="なんてことだ。"
/>

ツールの優先順位が高く、同時に設定するとツールの設定内容が表示されます。

非UIスレッドでのデータ変更

バインディングがLiveDataの場合、サブスレッドでのデータの更新はpostValueを使用して行うことができます。

バインディングがObservableXxxx型であれば、そのsetメソッドもサブスレッドで直接呼び出すことができ、DataBindingはサブスレッドでのデータ変更をサポートし、UIにも応答し、スレッドセーフですが、ObservableArrayMapのようなコレクション型はスレッドセーフではありません。

ですから、データがコレクション型であれば、データはメインスレッドで一律に変更されるべきです。

リスト製本

リストバインディングを簡単に実装できるサードパーティのバインディングライブラリ:binding-collection-adapter使うことをお勧めします。

独自のアダプタを作成する場合、最も重要なのは setVariable メソッドと executePendingBindings メソッドを使ってデータを更新することです。

コンバーター

例えば、TextViewのtextプロパティに設定する必要があるfloat値があり、textの要件がStringで、明らかにfloatが要件を満たさない場合、次のようにfloatをStringに変換するコンバータを定義することができます:

package com.imyyq.sample
import androidx.databinding.BindingConversion
@BindingConversion
fun convert(value: Float?) = value?.toString() + "リストを作るよ。"

入力パラメータは変換される型、出力パラメータは変換後の型、つまり Float と String です。

その後、xmlで使用することができます。

val mmMutableLiveData2 = MutableLiveData(100.222f)
<TextView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@{viewModel.mmMutableLiveData2}"
/>

多分xmlはエラーを報告するでしょう、これはDataBindingがまだ非常にインテリジェントではないという事実の反映ですが、それはすでに動作しています。上記は単なる例であり、要するに、この関数は、型を変換するために使用され、自動的に入力と出力のパラメータを一致させます。

以下のプロパティを設定すると混乱するかもしれません:

android:background="@{@android:color/black}"
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
 return new ColorDrawable(color);
}

ですから、もうコンバーターを定義する必要はありません。

ビューのメソッドをバインド

DataBindingライブラリは、android:textやandroid:onClickなど、よく使われる属性のサポートを追加しています。この問題を解決するには2つの方法があります。バインディングをカスタマイズする方法と、ビューのパブリックメソッド名を属性として使う方法です。

例として、TextViewはxmlにandroid:selectedプロパティを持っていません:

DataBindingを使用しない場合、つまり@{}で値をラップしない場合です:

android:selected="true"

そうすると、プロパティが見つからないため、実行すらできません。では、なぜDataBindingではうまくいくのでしょうか? TextViewにはsetSelectedメソッドがあるので、ViewにsetXxxxメソッドさえ用意すれば、カスタムViewであろうと公式Viewであろうと、xxxxプロパティ、つまりセットを外した後の最初の文字を小文字にすることができ、継承メソッドにも対応しているからです。

例えば、TextViewがViewから継承しているViewのsetAlphaメソッドはalphaプロパティを持つことができます。しかし、未定義のプロパティの先頭にandroid:を使うと、上記のように、うまくいっても黄色くなってしまうので、以下のように、先頭にapp:を使うのがよいでしょう:

例えば、EditTextのextendSelection(int index)メソッドはセットで始まるメソッドではないので、app:extendSelection="@{2}"で直接このメソッドにバインドできます。

デフォルトのメソッド名が気に入らない場合はどうすればいいでしょうか?次のような test というメソッドを持つカスタムビューがあるとしましょう:

@BindingMethods(value = [
 BindingMethod(
 type = CustomEditText::class,
 attribute = "myTest",
 method = "test")])
class CustomEditText(context: Context?, attrs: AttributeSet?) : AppCompatEditText(context, attrs) {
 fun test(index: Int = 0) {
 Log.i("CustomEditText", "commonLog - test: $index")
 }
}

そうすれば、app:test属性を使うこともできますし、myTest属性を直接使うこともできます。

<com.imyyq.sample.CustomEditText
 android:id="@+id/et"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="私は小さな蜂だ。"
 myTest="@{10086}"
 app:test="@{10086}"
/>

BindingMethod アノテーションを使用すると、プロパティの名前をカスタマイズして指定のメソッドにバインドできます。バインドされたデータが変更されると、メソッドが自動的に呼び出されます。

カスタム製本

上記では既存のメソッドにバインドする方法を説明しましたが、バインドするメソッドがパラメータレスの場合はどうでしょうか?例えば、EditTextのrequestFocus()メソッドは、xml属性が値を持っているため、明らかに属性でバインドすることはできません。

プロパティのロジックをカスタマイズしたいとします。例えば、TextViewのsetTextメソッドで、内容を設定するときに、内容の空白をすべて=記号に置き換えるという判定を行いたいのですが、もちろん、このニーズを実現する方法はたくさんありますが、ここでは一例として、どのように行うのでしょうか?

答えはBindingAdapterアノテーションです。答えはBindingAdapterアノテーションを通してです:

@BindingAdapter(value = ["changeText"])
fun changeText(view: TextView, text: String) {
 view.text = text.replace(" ", "=")
}

すると、TextViewは自動的に以下のようにchangeTextプロパティを持ちます:

<TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 changeText="@{viewModel.mmMutableLiveData.key2 + `なあ`}"
/>

単語と単語の間のスペースは=記号に置き換えられます。

注:ここでは接頭辞が指定されていないので、app:changeTextにはできません。

また、複数の属性を持つこともできます:

@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
}

requireAllがtrueの場合、バインディングを成功させるためには両方のプロパティが設定されている必要があります。requireAllがfalseの場合、valueで定義された属性の1つが設定されていればメソッドをバインドすることができますが、設定されていない他の値はnullであるため、パラメータがKotlinの場合はnullでなければなりません。

また、例えば古い値を受け取ることもできます:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
 if (oldPadding != newPadding) {
 view.setPadding(newPadding,
 view.getPaddingTop(),
 view.getPaddingRight(),
 view.getPaddingBottom())
 }
}
<TextView
 android:id="@+id/tv_msg"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:paddingLeft="@{viewModel.mmMutableLiveData3}"
 changeText="@{viewModel.mmMutableLiveData.key2 + `なあ`}"
 />

こうすることで、android:paddingLeftプロパティを使用する際、値が変更されるたびにBindingAdapterアノテーションのsetPaddingLeftメソッドが呼び出され、新旧両方の値が渡されます。

この場合、android: がネイティブの定義、app: がViewのメソッド、接頭辞なしのものがカスタムの定義となります。

カスタム・バインディング・パラメータの注意点

もしあなたが Kt の開発者であれば、 BindingAdapter アノテーションのメソッド・パラメータは nullable であるべきで、デフォルト・パラメータを使用しないことに注意してください:

@BindingAdapter(
 value = ["onClickCommand", "isInterval", "intervalMilliseconds"],
 requireAll = false
)
fun onClickCommand(
 view: View,
 clickCommand: View.OnClickListener,
 isInterval: Boolean = GlobalConfig.Click.gIsClickInterval,
 intervalMilliseconds: Int = GlobalConfig.Click.gClickIntervalMilliseconds
) {
 if (isInterval) {
 view.clickWithTrigger(intervalMilliseconds.toLong(), clickCommand)
 } else {
 view.setOnClickListener(clickCommand)
 }
}

上記のisIntervalとintervalMillisecondsはNULLにできないだけでなく、デフォルト・パラメータを持っています。しかし、Javaにはデフォルト・パラメータがないので、この2つのパラメータを設定しなくても、以下のようにデフォルト値が生成されます:

これはKtのデフォルト・パラメーターを無効にしてしまうので、nullableでデフォルト・パラメーターのないものにする必要があります。以下のようになります:

@BindingAdapter(
 value = ["onClickCommand", "isInterval", "intervalMilliseconds"],
 requireAll = false
)
fun onClickCommand(
 view: View,
 clickCommand: View.OnClickListener?,
 isInterval: Boolean?,
 intervalMilliseconds: Int?
) {
 var interval = isInterval
 // xmlが設定されていない場合は、グローバル設定が使用される。
 if (interval == null) {
 interval = GlobalConfig.Click.gIsClickInterval
 }
 // 時間が設定されていない場合は、グローバル・コンフィギュレーションを使用する。
 var milliseconds = intervalMilliseconds
 if (milliseconds == null) {
 milliseconds = GlobalConfig.Click.gClickIntervalMilliseconds
 }
 if (interval) {
 clickCommand?.let { view.clickWithTrigger(milliseconds.toLong(), it) }
 } else {
 view.setOnClickListener(clickCommand)
 }
}

これで正しいコードが生成されます:

カスタム・ツーウェイ・バインディング

EditTextの双方向バインディングについてはすでに説明しました。 一般的な組み込みプロパティでは@={}を使用できますが、カスタムViewの場合は双方向バインディングが必要なので、カスタマイズする必要があります。簡単な例を見てみましょう。

カスタムビューから始めましょう:

class CustomView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
 var data: String = ""
}

バインディングのプロパティを再度カスタマイズします:

@BindingAdapter("time")
fun setTime(view: CustomView, newValue: String) {
 Log.i("MyBindingConversion", "commonLog - setTime: $newValue")
 if (view.data != newValue) {
 view.data = newValue
 }
}
@InverseBindingAdapter(attribute = "time", event = "timeAttrChanged")
fun getTime(view: CustomView): String {
 Log.i("MyBindingConversion", "commonLog - getTime: ")
 return view.data
}
@BindingAdapter("timeAttrChanged")
fun setListeners(
 view: CustomView,
 attrChange: InverseBindingListener
) {
 Log.i("MyBindingConversion", "commonLog - setListeners: ")
 // クリック、スワイプ、ダブルクリック、長押しなど、要件に応じて、変更をリッスンするビューを設定する。
 view.setOnClickListener {
 view.data = "click"
 attrChange.onChange()
 }
}

上記は、CustomViewを定義し、@BindingAdapterを介してそのCustomViewにtimeプロパティを定義しています。つまり、プロパティが変更されると、setTimeが呼び出される、つまり、フォワードバインディングであることが理解できます

InverseBindingAdapter アノテーションは、time プロパティのリバース・バインディングを設定します。 リバース・バインディングをトリガするメソッドは、event = timeAttrChanged のように、event プロパティで指定することができ、これは 3 番目のメソッド setListeners に対応します。実際には、明示的に event を宣言する代わりに、上記の @BindingAdapter("timeAttrChanged") のように、定義された time 属性に AttrChanged を追加することもできます。

以下を使用してください:

val mmMutableLiveData3 = MutableLiveData("start")
mmMutableLiveData3.value = "end"
<com.imyyq.sample.CustomView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 time="@={viewModel.mmMutableLiveData3}"
 />

以下のようにログを実行してください:

I: commonLog - setTime: start
I: commonLog - setListeners: 
I: commonLog - setTime: end
I: commonLog - getTime: 
I: commonLog - setTime: click

まず、mmMutableLiveData3のデフォルト値はstartなので、startを渡してsetTimeが呼ばれます。次に、双方向バインディングなので、イベントリスナーメソッドsetListenersが呼ばれ、mmMutableLiveData3の値がendに変更され、再びsetTimeが呼ばれます。この処理はわかりやすいです。この処理はわかりやすいです。

そして、setListenersは、ビューのリスニング・イベントを定義し、私がビューをクリックすると、それはdataプロパティを "click "に変更し、次にattrChange.onChange()を呼び出します。同時にmmMutableLiveData3の値もclickに変更されます。

両方向のカスタムデータバインディングの完了の上。このようなアドレスを選択するなど、デッドループを引き起こさないように注意してください、一般的に使用されるホイールコンポーネントは、我々は、双方向のバインディングデッドループを引き起こすことを避ける必要があります。

おわりに

上記がDataBindingの全機能です。もし足りないものがあれば、遠慮なく追加してください!もしこの記事の機能が網羅されていないと感じたら、それに関連する他のコンテンツを検索してください。

個人的には、DataBindingは良いですが、それによって閉じ込められないように、時間の先頭に慣れていない、あなたがあなたの時間を取ることができ、本当に複雑なビジネスに遭遇し、あなたが最初に完了するためにLiveDataの方法を観察する使用することができますし、最適化するためにDataBindingの使用を検討し、あなたがそれに精通しているので、一度に一歩ずつ行くことができると思います。

この記事がDataBindingの使い方に慣れる助けになれば幸いです。

Read next

Nacos Configuration Centreについて知っておきたいこと

前回はNacosのレジストリについて、レジストリの一貫性プロトコル、サブスクリプションとレジストレーションの原理についてお話しましたが、興味のある方は前回の記事「知っておきたいNacosのレジストリ」をご覧ください。Nacosでは、別の機能が特に重要である構成センターがあり、ここで最初に具体的に構成センターを紹介するものではありませんが、まず第一に、甘い波を覚えておくことです。 非常に最初にいくつかの簡単な学習項目を行うには...

Mar 17, 2020 · 11 min read