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

11

我的目标是将Material Slider视图与我的viewmodel的MutableLiveData进行双向数据绑定:

   <com.google.android.material.slider.Slider
        ...
        android:value="@={viewmodel.fps}"
        ...
    />

当然,这不起作用是因为在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.

但是,它们为SeekBar提供了一个适配器:/androidx/databinding/adapters/SeekBarBindingAdapter.java

据我所知,双向数据绑定仅能与“progress”属性一起使用,而单向数据绑定需要两种属性:“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) {

    }

它不是建筑物:

Could not find event android:valueAttrChanged on View type Slider

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

android:value="@={viewmodel.fps}"

如果我在Slider类中找不到valueAttrChanged,该如何找到正确的属性添加到BindingAdapter中呢?

2个回答

11

让我们看一下SeekBarBindingAdaptersetOnSeekBarChangeListener()方法。它添加了四个不同的属性:{"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()
    }
}

并且布局:

...
<com.google.android.material.slider.Slider
    ...
    android:value="@={model.count}" />
...

你可以在这里找到完整的源码。


1
@StayCool 好的,希望能对你有所帮助!根据双向数据绑定文档的说明,应该设置一个listeners适配器来让数据绑定系统知道值已经改变。如果你只提供了一个getter和一个setter,它可以获取和设置值,但无法确定何时值发生了改变。 - Valeriy Katkov
1
@StayCool属性行的text之所以有效,是因为它们具有内置的双向数据绑定属性。但是,材料滑块没有配置属性。根据这个 GitHub问题,Android材料组件不提供预配置的适配器。至于android:value,我猜它与一些现有定义冲突了,但不知道确切的原因。 - Valeriy Katkov
2
@StayCool 请查看更新后的答案。最终我弄清楚了如何使用 value 属性,它允许我们摆脱自定义的 setter 适配器。我还更新了示例存储库。如果您仍有疑问,请随时提问。 - Valeriy Katkov
2
谢谢你花费时间并提供如此详细的答案!但我还有两个问题,它们实际上超出了问题的范围,所以我不认为你会回答:
  1. androix.databinding 适配器是如何使用“android”命名空间来命名它们的属性名称的,为什么我们不能这样做?它们似乎重写了来自android命名空间的value属性?[链接](https://android.googlesource.com/platform/frameworks/data-binding/+/master/extensions/baseAdapters/src/main/java/android/databinding/adapters/SeekBarBindingAdapter.java)2. 每次滑块触发valueAttrChanged时,都会设置新的监听器吗?
- StayCool
2
@StayCool 很高兴能帮到你。setSliderListeners()在视图初始化时只会被调用一次,你可以通过向函数中添加日志来进行检查。绑定适配器在绑定值发生变化时会被调用,但在双向数据绑定的情况下,valueAttrChanged只会被初始化一次。至于android命名空间,它是保留给内置的Android属性的,应用程序特定的属性应该使用'app'命名空间。我无法说这个规则是如何被AndroidX库绕过的,但AndroidX是Android SDK的一部分,所以我认为他们知道一些秘密 :) - Valeriy Katkov
显示剩余6条评论

0

更新 Android Java

数据绑定

<variable
        name="device"
        type=".....Device" />

在绑定文件中

@InverseBindingAdapter(attribute = "android:value")
public Float getSlider(Slider slider) {
    return slider.getValue();
}

@BindingAdapter("app:valuesAttrChanged")
public void setSliderListeners(Slider slider, InverseBindingListener attrChange) {
    slider.addOnChangeListener(new Slider.OnChangeListener() {
        @Override
        public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
            attrChange.onChange();
        }
    });
}

在 XML 文件中添加一行

android:value="@{device.data}"

数据是值的变化


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接