karnbo
karnbo

Reputation: 191

Android EditText-Slider using 2 way binder (Float)

How to create a Slider to set a float value, and an EditText reflecting that float value, leave both of them connected to a ViewModel object float value, that is updated on the run. The float value should be possible to set both from the Slider and the EditText box.

EditText dialog with a Slider below it

I find it cumbersome to

  1. enter the decimal in the float number in the EditText box (Converter issue converting the float to a string)

Upvotes: 0

Views: 81

Answers (1)

karnbo
karnbo

Reputation: 191

The solution I found was to 1) skip the Converter and use a BindingAdapter and InverseBindingAdapter to handle the float value in the EditText android:text field, and 2) occasionally avoid updating the setText in the BindingAdapter (value passed from view model, to setText in the VIEW) - this will leave a half written float (string) intact.

essense from the xml file:

<data>
    <variable name="model" type="com....viewmodel.MyViewModel"/>
</data>
...
  <EditText
   android:id="@+id/someId"
   ...
   android:text="@={model.count}"
   android:selectAllOnFocus="true"
   android:digits="0123456789."/>
  
  <com.google.android.material.slider.Slider
   android:id="@+id/someSlider"
   ...
   android:valueTo="@{model.max}"
   android:value="@={model.count}" />

This is included to display the 2 way binder float connection to the model object.

Essence from my SliderAdapter.kt file:

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

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

This binder will process the Slider value set/get in the xml .

The following Binder will handle the value set/get in the EditText, before value is passed to the model object. Avoid updating the text in the gui when not needed is crucial.

@BindingAdapter("android:text")
fun setText(view: EditText, value: Float?) {
  if (value == null) return

  var decimals: Int = (value * 100).roundToInt() % 100
  decimals = if (decimals == 0) 0 else
    if (decimals % 10 == 0) 1 else 2

  val oldVal = view.text.toString().toFloatOrNull() ?: 0F
  val valRounded : Float = (value * 100)/100

  if (valRounded != oldVal)
    view.setText(String.format("%." + decimals + "f", value))
  // Avoid updating the text if last character was a '.'

  view.setSelection(view.text.length);
  // This line will place the marker at the end of the written text.
}

@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
fun getTextString(view: EditText): Float {
  return view.text.toString().toFloatOrNull() ?: 0F
}

And finally, if anyone is still with me, the model kotlin file:

class MyViewModel : ViewModel() {
  val max : Float = 10.5F
  val count = MutableLiveData(2.0f)
    get() {
      if(field.value!! > max)
        field.value = max
      return field
    }
}

Voila, its now possible to enter a half written float:

enter image description here

Upvotes: 0

Related Questions