使用 Android 架构组件将单向数据绑定转换为双向数据绑定
Convert one-way data binding to two-way using Android Architecture Components
我正在为一个大学项目重构我的 Android 应用程序以使用架构组件,但我很难在 SwitchCompat
上实现双向数据绑定。该应用程序有一个简单的用户界面,其中 TextView
显示位置更新的状态和前面提到的 SwitchCompat
,它可以打开和关闭位置更新。
现在我在 SwitchCompat
的 checked
属性上使用单向数据绑定,但我想使用 two-way databinding.
当前使用模型-视图-视图模型架构的实现如下:
MainViewModel.java:
public class MainViewModel extends ViewModel {
private LiveData<Resource<Location>> mLocationResource;
public MainViewModel() {
mLocationResource = Repository.getInstance().getLocationResource();
}
public LiveData<Resource<Location>> getLocationResource() {
return mLocationResource;
}
public void onCheckedChanged (Context context, boolean isChecked) {
if (isChecked) {
Repository.getInstance().requestLocationUpdates(context);
} else {
Repository.getInstance().removeLocationUpdates(context);
}
}
}
Resource(看到了想法 here)是一个 class 保存可为空的数据(位置)和 TextView
可以处理的非空状态:
State.java
public enum State {
LOADING,
UPDATE,
UNKNOWN,
STOPPED
}
现在 android:onCheckedChanged
在 fragment_main.xml 中实现:
android:onCheckedChanged="@{(buttonView, isChecked) -> viewModel.onCheckedChanged(context, isChecked)}"
最后是将状态转换为布尔检查状态的自定义绑定适配器:
@BindingAdapter({"android:checked"})
public static void setChecked(CompoundButton view, Resource<Location> locationResource) {
State state = locationResource.getState();
boolean checked;
if (state == State.STOPPED) {
checked = false;
} else {
checked = true;
}
if (view.isChecked() != checked) {
view.setChecked(checked);
}
}
和android:checked
属性在fragment_main.xml中的实现:
android:checked="@{viewModel.getLocationResource}"
正如我上面链接的 Android 开发人员指南所说,我如何在 android:checked
中完成所有工作,而不是同时拥有 android:checked
和 android:onCheckedChanged
(一个-方式数据绑定到双向数据绑定)?
另外,如果您认为我的应用程序的 architecture/logic 可以改进,请告诉我:)
以下是我的做法(对 Kotlin 代码感到抱歉):
首先,我将重构 Resource<T>
class 并使 state
变量成为 MutableLiveData<State>
对象:
enum class State {
LOADING,
UPDATE,
UNKNOWN,
STOPPED
}
class Resource<T>() {
var state = MutableLiveData<State>().apply {
value = State.STOPPED //Setting the initial value to State.STOPPED
}
}
然后我将创建以下 ViewModel:
class MainViewModel: ViewModel() {
val locationResource = Resource<Location>()
}
在数据绑定布局中,我将编写以下内容:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:resourceState="@={viewModel.locationResource.state}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.locationResource.state)}" />
</LinearLayout>
</layout>
注意 SwitchCompat
视图中的双向数据绑定表达式 @=
。
现在 BindingAdapter
和 InverseBindingAdapter
:
@BindingAdapter("resourceState")
fun setResourceState(compoundButton: CompoundButton, resourceState: State) {
compoundButton.isChecked = when (resourceState) {
// You can decide yourself how the mapping should look like:
State.LOADING -> true
State.UPDATE -> true
State.UNKNOWN -> true
State.STOPPED -> false
}
}
@InverseBindingAdapter(attribute = "resourceState", event = "resourceStateAttrChanged")
fun getResourceStateAttrChanged(compoundButton: CompoundButton): State =
// You can decide yourself how the mapping should look like:
if (compoundButton.isChecked) State.UPDATE else State.STOPPED
@BindingAdapter("resourceStateAttrChanged")
fun setResourceStateAttrChanged(
compoundButton: CompoundButton,
attrChange: InverseBindingListener
) {
compoundButton.setOnCheckedChangeListener { _, isChecked ->
attrChange.onChange()
// Not the best place to put this, but it will work for now:
if (isChecked) {
Repository.getInstance().requestLocationUpdates(context);
} else {
Repository.getInstance().removeLocationUpdates(context);
}
}
}
就是这样。现在:
- 每当
locationResource.state
更改为 State.STOPPED
时,SwitchCompat
按钮将进入未选中状态。
- 每当
locationResource.state
从 State.STOPPED
变为另一种状态时, SwitchCompat
按钮将变为选中状态。
- 每当点击
SwitchCompat
按钮并更改为选中状态时,locationResource.state
的值将更改为 State.UPDATE
。
- 每当点击
SwitchCompat
按钮并更改为未选中状态时,locationResource.state
的值将更改为 State.STOPPED
。
如果有什么不清楚的地方,请随时问我。
最后,我放弃了从单向数据绑定转换为双向数据绑定的尝试,但我设法稍微简化了 android:checked
属性:
我替换了值
"@{viewModel.getLocationResource}"
和
"@{viewModel.locationResource.state != State.STOPPED}"
并完全删除 android:checked
@BindingAdapter
.
我正在为一个大学项目重构我的 Android 应用程序以使用架构组件,但我很难在 SwitchCompat
上实现双向数据绑定。该应用程序有一个简单的用户界面,其中 TextView
显示位置更新的状态和前面提到的 SwitchCompat
,它可以打开和关闭位置更新。
现在我在 SwitchCompat
的 checked
属性上使用单向数据绑定,但我想使用 two-way databinding.
当前使用模型-视图-视图模型架构的实现如下:
MainViewModel.java:
public class MainViewModel extends ViewModel {
private LiveData<Resource<Location>> mLocationResource;
public MainViewModel() {
mLocationResource = Repository.getInstance().getLocationResource();
}
public LiveData<Resource<Location>> getLocationResource() {
return mLocationResource;
}
public void onCheckedChanged (Context context, boolean isChecked) {
if (isChecked) {
Repository.getInstance().requestLocationUpdates(context);
} else {
Repository.getInstance().removeLocationUpdates(context);
}
}
}
ResourceTextView
可以处理的非空状态:
State.java
public enum State {
LOADING,
UPDATE,
UNKNOWN,
STOPPED
}
现在 android:onCheckedChanged
在 fragment_main.xml 中实现:
android:onCheckedChanged="@{(buttonView, isChecked) -> viewModel.onCheckedChanged(context, isChecked)}"
最后是将状态转换为布尔检查状态的自定义绑定适配器:
@BindingAdapter({"android:checked"})
public static void setChecked(CompoundButton view, Resource<Location> locationResource) {
State state = locationResource.getState();
boolean checked;
if (state == State.STOPPED) {
checked = false;
} else {
checked = true;
}
if (view.isChecked() != checked) {
view.setChecked(checked);
}
}
和android:checked
属性在fragment_main.xml中的实现:
android:checked="@{viewModel.getLocationResource}"
正如我上面链接的 Android 开发人员指南所说,我如何在 android:checked
中完成所有工作,而不是同时拥有 android:checked
和 android:onCheckedChanged
(一个-方式数据绑定到双向数据绑定)?
另外,如果您认为我的应用程序的 architecture/logic 可以改进,请告诉我:)
以下是我的做法(对 Kotlin 代码感到抱歉):
首先,我将重构 Resource<T>
class 并使 state
变量成为 MutableLiveData<State>
对象:
enum class State {
LOADING,
UPDATE,
UNKNOWN,
STOPPED
}
class Resource<T>() {
var state = MutableLiveData<State>().apply {
value = State.STOPPED //Setting the initial value to State.STOPPED
}
}
然后我将创建以下 ViewModel:
class MainViewModel: ViewModel() {
val locationResource = Resource<Location>()
}
在数据绑定布局中,我将编写以下内容:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:resourceState="@={viewModel.locationResource.state}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.locationResource.state)}" />
</LinearLayout>
</layout>
注意 SwitchCompat
视图中的双向数据绑定表达式 @=
。
现在 BindingAdapter
和 InverseBindingAdapter
:
@BindingAdapter("resourceState")
fun setResourceState(compoundButton: CompoundButton, resourceState: State) {
compoundButton.isChecked = when (resourceState) {
// You can decide yourself how the mapping should look like:
State.LOADING -> true
State.UPDATE -> true
State.UNKNOWN -> true
State.STOPPED -> false
}
}
@InverseBindingAdapter(attribute = "resourceState", event = "resourceStateAttrChanged")
fun getResourceStateAttrChanged(compoundButton: CompoundButton): State =
// You can decide yourself how the mapping should look like:
if (compoundButton.isChecked) State.UPDATE else State.STOPPED
@BindingAdapter("resourceStateAttrChanged")
fun setResourceStateAttrChanged(
compoundButton: CompoundButton,
attrChange: InverseBindingListener
) {
compoundButton.setOnCheckedChangeListener { _, isChecked ->
attrChange.onChange()
// Not the best place to put this, but it will work for now:
if (isChecked) {
Repository.getInstance().requestLocationUpdates(context);
} else {
Repository.getInstance().removeLocationUpdates(context);
}
}
}
就是这样。现在:
- 每当
locationResource.state
更改为State.STOPPED
时,SwitchCompat
按钮将进入未选中状态。 - 每当
locationResource.state
从State.STOPPED
变为另一种状态时,SwitchCompat
按钮将变为选中状态。 - 每当点击
SwitchCompat
按钮并更改为选中状态时,locationResource.state
的值将更改为State.UPDATE
。 - 每当点击
SwitchCompat
按钮并更改为未选中状态时,locationResource.state
的值将更改为State.STOPPED
。
如果有什么不清楚的地方,请随时问我。
最后,我放弃了从单向数据绑定转换为双向数据绑定的尝试,但我设法稍微简化了 android:checked
属性:
我替换了值
"@{viewModel.getLocationResource}"
和
"@{viewModel.locationResource.state != State.STOPPED}"
并完全删除 android:checked
@BindingAdapter
.