ビューモデル
UIデータを保持する
アクティビティ/フラグメントはデータの表示のみを行い、ユーザー操作を処理します。
ViewModel は UI データを保持します。
ライフサイクル
Activity の再構築は ViewModel のライフサイクルには影響しません。
ViewModel のライフサイクル関数は 1 つだけありonCleared()
、この関数は、Activity によって表されるページが破棄された場合にのみ呼び出されます。
ViewModel引用Context
ViewModel のライフ サイクルは Activity のライフ サイクルよりも長いため、ViewModel で Activity への参照を保持しないでください。保持しないと、メモリ リークが発生します。
ViewModel は Activity の導入を推奨していませんが、ViewModel で Context が必要な場合はどうすればよいでしょうか? 次のいずれかの方法を使用できます:
1. を使用しますContext.getApplicationContext()
;
2. ViewModel のサブクラスであり、Application を内部で Context として受け取る AndroidViewModel を使用します。
ViewModelのインスタンス化
XXXViewModel mXXXViewModel = new ViewModelProvider(this).get(XXXViewModel.class);
ViewModel と onSaveInstanceState() の違い
-
データの違い
onSaveInstanceState() はシリアル化できる少量の UI データしか保存できず、Bitmap のような大きなデータは保存できません。
ViewModel にはそのような制限はありません。 -
データ永続性
onSaveInstanceState() は、次の 2 つの状況で少量の UI データを保存できます。
① バックグラウンドでアプリケーションのプロセスがメモリ制限により終了した。
② 設定変更。
ViewModel は、終了したプロセス間ではなく、構成変更時に破棄された場合にのみデータを保持できます。
ライブデータ
使用
LiveData はデータ コンテナとして理解できます。データがオブザーバーになるようにデータをラップし、データが変更されたときにオブザーバーに通知できるようにします。
ViewModel は UI データを保持し、Activity/Fragment はデータの表示を担当します。UI データが変更された場合、LiveData はデータを更新するように Activity/Fragment に通知します。したがって、LiveData は通常 ViewModel で使用されます。
基本的な使い方
LiveData は抽象クラスであるため、直接使用することはできません。通常、そのサブクラス MutableLiveData を使用します。
LiveData.observe() メソッドを通じて、LiveData によってラップされたデータを観察します。逆に、LiveData によってラップされたデータを変更したい場合は、LiveData.postValue()/LiveData.setValue() メソッドを通じて実行できます。postValue() は非 UI スレッドで呼び出され、setValue() は UI スレッドで呼び出されます。
通知更新
LiveData はページのライフ サイクルを認識でき、ページがアクティブ状態 (Lifecycle.State.STARTED または Lifecycle.State.RESUMED) にある場合にのみ LiveData 通知を受信します。 DESTROYED) の場合、LiveData はページとの関連付けを自動的にクリアし、メモリ リークを回避します。
通常、LiveData は、データが変更されたときにのみ、アクティブなオブザーバーにのみ更新を送信します。この動作の例外は、オブザーバーが非アクティブからアクティブに変化したときにも更新を受信することです。また、オブザーバーが非アクティブからアクティブに 2 回目に変更された場合、オブザーバーは最後にアクティブになってから値が変更されている場合にのみ更新を受け取ります。
LiveData.observeForever() は観察と同じ方法で使用されますが、違いは、データが変更されたときに、ページがどのような状態であっても通知を受信できることです。したがって、オブザーバーを削除してメモリ リークを回避するには、使用後に RemoveObserver() を呼び出す必要があります。
ViewModel+LiveData はフラグメント間の通信を実装します
public class OneFragment extends Fragment {
public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
关键在于ViewModelProvider的构造函数传入的是getActivity()而不是Fragment.this,
这样才能保证每个Fragment得到的是同一个ViewModel,从而共享LiveData
*/
XXXViewModel mXXXViewModel = new ViewModelProvider(getActivity()).get(XXXViewModel.class);
}
}
//TwoFragment与OneFragment类似
まとめ
LiveData の本質は、オブザーバー モード + 知覚ライフ サイクルです。
データバインディング
使いやすい
- データバインディングの開始
android {
……
dataBinding {
enabled = true;
}
}
- タグ レイアウト ファイル
レイアウト ファイルのルートの外側にタグを追加します<layout>
。この目的は、レイアウト ファイルに対応する Binding クラスを生成するように DataBinding ライブラリに指示することです。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
/*
以下是实际布局
……
*/
</layout>
- レイアウト変数を定義する
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name = "变量名"
type = "类全名"/>
/*
或者使用<import>标签引入类
<import type = "类全名"/>
<variable
name = "变量名"
type = "类名称"/>
*/
</data>
/*
以下是实际布局
……
*/
</layout>
- バインディングクラスを取得する
//该方法给Activity设置布局文件的同时,返回Binding类。
XXXBinding mXXXBinding = DataBindingUtil.setContentView(this, R.layout.xxx);
-
レイアウト変数の代入 バインディング
では、レイアウト変数に値を代入する 2 つの方法が用意されています。
①一般的な方法:XXXBinding.setVariable(BR.变量名, 变量);
②特定のレイアウト変数への代入方法:XXXBinding.set变量名(变量)
-
レイアウト式
レイアウト式の形式:@{}
。
例:@{布局变量.字段}
、@{方法调用的表达式}
<data>
<import type = "xxx.xxx.TestUtil"/>
<variable
name = "book"
type = "xxx.xxx.Book"/>
</data>
<!-- 在布局中引用静态类-->
<TextView
android:text="@{TestUtil.getText()}"/>
<TextView
android:text="@{book.name}"/>
レイアウト式はこれらの使用法をはるかに超えています。詳細については、「データ バインディング詳細説明 (2) - レイアウトとバインディング式」を参照してください。
- アクティビティ最終出演
public class TestActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
XXXBinding mXXXBinding = DataBindingUtil.setContentView(this, R.layout.xxx);
Book book = new Book();
book.name = "Jetpack应用指南";
mXXXBinding.setBook(book);
}
}
イベントバインディング
DataBinding は、View イベント応答を処理するためのレイアウト式の使用をサポートしています。
具体的な方法: レイアウト ファイル内の View のイベント属性にレイアウト式を割り当てます。
これは、レイアウト式を使用して、対応するリスナーのコールバックを実装することと同じです。この方法は、イベント バインディングとして知られています。
イベント属性とリスナーの対応
イベント属性名はリスナーメソッド名によって異なります。たとえば、View.OnClickListener にはonClick()
メソッドがあり、View.OnLongClickListener にはonLongClick()
メソッドがあるため、イベントのプロパティはandroid:onClick
、となりますandroid:onLongClick
。
クリック イベントについては、複数のクリック イベントの競合を避けるために、Google は次のような特別なイベント処理も定義しています。
クラス | リスナーの設定方法 | バインディングのプロパティ |
---|---|---|
検索ビュー | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ズームコントロール | setOnZoomInClickListener(View.OnClickListener) | アンドロイド:onZoomIn |
ズームコントロール | setOnZoomOutClickListener(View.OnClickListener) | アンドロイド:onZoomOut |
イベント バインディングのレイアウト式には、参照メソッドとバインディング リスナーの 2 種類があります。
参照方法
レイアウト変数を使用してイベントに応答します。このメソッドでは、パラメータと戻り値がリスナーのパラメータと戻り値と一致する必要があります。パラメータまたは戻り値が一致しない場合、コンパイル時にエラーが報告されます。
public class EventHandler {
public void onClickHandle(View view) {
System.out.println("按钮被点击了");
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="eventHandler"
type="xxx.xxx.EventHandler" />
</data>
...
<Button
...
android:onClick="@{eventHandler::onClickHandle}"
... />
...
</layout>
Reference Method を使用する場合、生成されたリスナーはレイアウト変数のメソッド呼び出しをカプセル化します。リスナー オブジェクトは、レイアウト変数が設定されるときに作成され、割り当てられます。レイアウト変数が null の場合、リスナーは作成されません。
/*
“引用方法”的监听器创建原理如下伪代码所示
伪代码是在运行时运行的。
*/
xxxBinding.setEventHandler(EventHandler eventHandler) {
if(eventHandler != null) {
button.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
eventHandler.onClickHandle(view);
}
});
}
}
バインドリスナー
レイアウト ファイルでラムダを使用してイベントに応答します。このメソッドでは、戻り値がリスナーの期待される戻り値と一致することのみが必要です。
public class Tester {
public boolean testLongClick() {
return false;
}
}
<Button
...
android:onClick="@{()->tester.testLongClick()}"
... />
バインディング リスナーではカスタム パラメーターが可能です。
public class Tester {
public boolean testLongClick(View v, String info) {
Toast.makeText(v.getContext(), info, Toast.LENGTH_LONG).show();
}
}
<Button
...
android:onClick="@{(view)->tester.testLongClick(view, '你好')}"
... />
「Bind Listener」は、コンパイル時に必要なリスナーを自動的に作成し、そのリスナーに対するイベントを登録します(リスナーは最初に作成され、トリガーされるまでレイアウト変数が空かどうか判断されず、空の場合は実行されません)空いてますので操作してください)
/*
“绑定监听器”的监听器创建原理如下伪代码所示
伪代码是在编译时运行的。
*/
button.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if(tester != null) {
tester.testLongClick(view, "你好");
}
}
});
三項式
述語を含む式 (たとえば、三項式) を使用する必要がある場合は、onCLick 属性の void や onLongClick 属性の Boolean など、リスナーと一致する戻り値の型を式として使用できます。
android:onClick="@{(view)->view.isEnabled()?activity.showSign(view, user):void}"
android:onLongClick="@{(v)->v.isEnabled()?activity.showSign(user):false}"
二次ページのバインド
アクティビティ/フラグメントによって直接参照されるページを第 1 レベルのページと呼び、第 1 レベルのページ内のラベルによって参照されるページを第 2 レベルのページと呼びます。
レイアウト変数を第 1 レベルのページから第 2 レベルのページに渡すにはどうすればよいですか?
レイアウト変数が第 1 レベルのレイアウトで定義されると、その変数は第 1 レベルのレイアウトで受け取って使用できるだけでなく、book
名前空間xmlns:app
の属性にもなります。
この属性の目的は、book
レイアウト変数をセカンダリ レイアウトに渡すことです。
//一级页面
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name = "book"
type = "xxx.xxx.Book"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/layout_content"
app:book="@{book}">
</LinearLayout>
</layout>
//二级页面
/**
在二级页面layout_content中,需要定义一个与一级页面相同的布局变量,
用来接收传递过来的数据。收到book变量后即可使用该变量了。
*/
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name = "book"
type = "xxx.xxx.Book"/>
</data>
<TextView
……
android:text="@{book.name}"/>
</layout>
バインディングアダプター
バインディング アダプター (BindingAdapter) は、レイアウト内の属性式を、値を設定するための対応するメソッド呼び出しに変換します。いわゆる設定値は、①setText()メソッドの呼び出しなど属性値の設定②setOnClickListener()メソッドの呼び出しなどイベントリスナーの設定の2種類に分かれます。また、値を設定する呼び出しメソッドをカスタマイズし、独自のバインド ロジックを提供することもできます。
DataBinding ライブラリの BindingAdapter
多くの XXXBindingAdapter クラスが DataBinding ライブラリで提供されており、Android ネイティブ コントロールが属性式をサポートできるようになります。
//DataBinding库下ViewBindingAdapter的部分源码
public class ViewBindingAdapter {
@BindingAdapter({
"android:padding"})
public static void setPadding(View view, float paddingFloat) {
final int padding = pixelsToDimensionPixelSize(paddingFloat);
view.setPadding(padding, padding, padding, padding);
}
}