使用 Android 架构组件将单向数据绑定转换为双向数据绑定

Convert one-way data binding to two-way using Android Architecture Components

我正在为一个大学项目重构我的 Android 应用程序以使用架构组件,但我很难在 SwitchCompat 上实现双向数据绑定。该应用程序有一个简单的用户界面,其中 TextView 显示位置更新的状态和前面提到的 SwitchCompat,它可以打开和关闭位置更新。
现在我在 SwitchCompatchecked 属性上使用单向数据绑定,但我想使用 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:onCheckedChangedfragment_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:checkedandroid: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 视图中的双向数据绑定表达式 @=

现在 BindingAdapterInverseBindingAdapter

@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.stateState.STOPPED 变为另一种状态时, SwitchCompat 按钮将变为选中状态。
  • 每当点击 SwitchCompat 按钮并更改为选中状态时,locationResource.state 的值将更改为 State.UPDATE
  • 每当点击 SwitchCompat 按钮并更改为未选中状态时,locationResource.state 的值将更改为 State.STOPPED

如果有什么不清楚的地方,请随时问我。

最后,我放弃了从单向数据绑定转换为双向数据绑定的尝试,但我设法稍微简化了 android:checked 属性:
我替换了值

"@{viewModel.getLocationResource}"

"@{viewModel.locationResource.state != State.STOPPED}"

并完全删除 android:checked @BindingAdapter.