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

时间:2019-02-03 18:58:32

标签: android android-databinding android-architecture-components android-jetpack two-way-binding

我正在将我的Android应用重构为一个大学项目,以使用架构组件,并且在SwitchCompat上实现双向数据绑定非常困难。该应用程序具有一个简单的用户界面,其中包含显示位置更新状态的TextView和前面提到的SwitchCompat,可打开和关闭位置更新。
目前,我在SwitchCompat的{​​{1}}属性上使用单向数据绑定,但想使用two-way databinding
使用Model-View-ViewModel体系结构的当前实现如下:
MainViewModel.java

checked

Resource (见到想法here)是一个类,其中包含可为空的数据(位置)和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); } } } 可处理的非空状态:
State.java

TextView

现在在 fragment_main.xml 中的public enum State { LOADING, UPDATE, UNKNOWN, STOPPED } 实现:

android:onCheckedChanged

最后是自定义绑定适配器,可以从状态转换为布尔检查状态:

android:onCheckedChanged="@{(buttonView, isChecked) -> viewModel.onCheckedChanged(context, isChecked)}"

以及 fragment_main.xml 中的@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

正如我上面链接的《 Android开发人员指南》所述,如何在android:checked="@{viewModel.getLocationResource}" 内完成所有工作,而不是同时拥有android:checkedandroid:checked(单向绑定到双向数据绑定)?
另外,如果您认为我的应用程序的体系结构/逻辑可以改进,请告诉我:)

2 个答案:

答案 0 :(得分:1)

这是我要怎么做(对不起Kotlin代码):

首先,我将重构Resource<T>类,并使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.STOPPEDSwitchCompat按钮都会进入未选中状态。
  • 只要locationResource.stateState.STOPPED变为另一状态,SwitchCompat按钮将进入已检查状态。
  • 只要轻按SwitchCompat按钮并将其更改为选中状态,则locationResource.state的值将更改为State.UPDATE
  • 只要轻按SwitchCompat按钮并将其更改为未选中状态,则locationResource.state的值将更改为State.STOPPED

如果有不清楚的地方,随时问我任何问题。

答案 1 :(得分:1)

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

"@{viewModel.getLocationResource}"

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

并完全删除了android:checked @BindingAdapter