如何为 material.Slider 视图创建绑定适配器?

Sta*_*ool 9 data-binding android slider kotlin android-binding-adapter

我的目标是从我的视图模型将 2 路数据绑定 material.Slider 视图到 MutableLiveData:

   <com.google.android.material.slider.Slider
        ...
        android:value="@={viewmodel.fps}"
        ...
    />
Run Code Online (Sandbox Code Playgroud)

当然,这是行不通的,因为 androidx.databinding 库中没有用于 Slider 的数据绑定适配器

[databinding] Cannot find a getter for <com.google.android.material.slider.Slider android:value> that accepts parameter type <java.lang.Integer>. If a binding adapter provides the getter, check that the adapter is annotated correctly and that the parameter type matches.
Run Code Online (Sandbox Code Playgroud)

但是,他们有一个 SeekBar:/androidx/databinding/adapters/SeekBarBindingAdapter.java

据我了解,2路数据绑定应该只适用于“progress”属性,而1路数据绑定需要两个属性:“onChanged”和“progress”

我尝试为 Slider 调整 SeekBarBindingAdapter:

    @InverseBindingMethods({
            @InverseBindingMethod(type = Slider.class, attribute = "android:value"),
    })
    public class SliderBindingAdapter {
        @BindingAdapter("android:value")
        public static void setValue(Slider view, int value) {
            if (value != view.getValue()) {
                view.setValue(value);
            }
        }

@BindingAdapter(value = {"android:valueAttrChanged", "android:onValueChange"}, requireAll = false)
    public static void setOnSliderChangeListener(Slider view, final Slider.OnChangeListener valChanged, final InverseBindingListener attrChanged) {
        if (valChanged == null)
            view.addOnChangeListener(null);
        else
            view.addOnChangeListener((slider, value, fromUser) -> {
                if (valChanged != null)
                    valChanged.onValueChange(slider, value, fromUser);
            });


        if (attrChanged != null) {
            attrChanged.onChange();
        }
    }

    @Override
    public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {

    }
Run Code Online (Sandbox Code Playgroud)

这不是建筑:

Could not find event android:valueAttrChanged on View type Slider
Run Code Online (Sandbox Code Playgroud)

但是如果我只使用它,为什么它会寻找 valueAttrChanged

android:value="@={viewmodel.fps}"
Run Code Online (Sandbox Code Playgroud)

?

如果在 Slider 类中没有看到 valueAttrChanged,如何找到要添加到 BindingAdapter 的正确属性?

Val*_*kov 5

我们来看看SeekBarBindingAdapter 的 setOnSeekBarChangeListener()方法。它添加了四个不同的属性:{"android:onStartTrackingTouch", "android:onStopTrackingTouch", "android:onProgressChanged", "android:progressAttrChanged"}但只有最后一个被双向数据绑定使用。

但是为什么有四个属性呢?如果您查看SeekBar类,它具有setOnSeekBarChangeListener()允许您设置和删除侦听器的方法。问题在于 SeekBar 只能有一个侦听器,并且该侦听器提供不同的回调:onProgressChangedonStartTrackingTouchonStopTrackingTouch

SeekBarBindingAdapter注册自己的侦听器,这意味着没有人可以在不删除现有侦听器的情况下注册另一个侦听器。这就是为什么SeekBarBindingAdapter提供onStartTrackingTouchonStopTrackingTouchonProgressChanged属性,因此你可以听这些事件没有注册自己OnSeekBarChangeListener

实际上,Slider适配器可以比 简单得多SeekBarBindingAdapter,因为 Slider 允许您使用addOnChangeListener()和来添加和删除侦听器removeOnChangeListener()。因此,双向数据绑定适配器可以注册自己的侦听器,其他任何人都可以注册其他侦听器而无需删除以前的侦听器。

它允许我们定义一个非常简洁的适配器。我创建了一个 kotlin 示例,希望你能把它翻译成 java:

@InverseBindingAdapter(attribute = "android:value")
fun getSliderValue(slider: Slider) = slider.value

@BindingAdapter("android:valueAttrChanged")
fun setSliderListeners(slider: Slider, attrChange: InverseBindingListener) {
    slider.addOnChangeListener { _, _, _ ->
        attrChange.onChange()
    }
}
Run Code Online (Sandbox Code Playgroud)

和布局:

...
<com.google.android.material.slider.Slider
    ...
    android:value="@={model.count}" />
...
Run Code Online (Sandbox Code Playgroud)

您可以在此处找到完整的资源。

  • @StayCool 请查看更新的答案。最后我弄清楚了如何使用“value”属性,它允许我们摆脱自定义的 *setter* 适配器。我还更新了示例存储库。如果您仍有疑问,请随时提问。 (2认同)
  • 感谢您的宝贵时间和如此详细的回答!但我还有两个问题,实际上超出了主题问题,所以我认为您不会回答:1. androix.databinding 适配器如何使用“android”命名空间作为其 attrs 名称,以及为什么我们不能?它们是重写 android 命名空间中的 value 属性吗?[链接](https://android.googlesource.com/platform/frameworks/data-binding/+/master/extensions/baseAdapters/src/main/java/android/databinding/adapters/SeekBarBindingAdapter.java) 2. 是新的每次滑块触发 valueAttrChanged 时都会设置侦听器吗? (2认同)
  • @StayCool 很高兴为您提供帮助。`setSliderListeners()` 在初始化时每个视图仅调用一次,您可以通过向函数添加日志来检查它。每次更改绑定值时都会调用绑定适配器,但在双向数据绑定的情况下,“valueAttrChanged”仅初始化一次。那么“android”命名空间呢,它是为内置的 android 属性保留的,应用程序特定的属性应该使用“app”命名空间。我不能说这个规则是如何被 androidx 库绕过的,但 androidx 它是 android sdk 的一部分,所以我认为他们知道一些秘密:) (2认同)