Is it possible to use data-binding for a SeekBar or ProgressBar in Android? I have this data element:
In case it helps someone, this question comes up under search a lot, this can now be done using two-way databinding. I use two bindable values below, I tend to prefer no display logic in the XML itself, but I suspect it can work with just one as well.
Create the bindable values in your ViewModel, one for the SeekBar
(seekValue) and one for the TextView
(seekDisplay)
int mSeekValue;
int mSeekDisplay;
@Bindable
public int getSeekValue() {
return mSeekValue;
}
public void setSeekValue(int progress) {
mSeekValue = progress;
notifyPropertyChanged(BR.seekValue);
setSeekDisplay(progress);
}
@Bindable
public String getSeekDisplay() {
return Integer.toString(mSeekDisplay);
}
public void setSeekDisplay(int progress) {
mSeekDisplay = progress;
notifyPropertyChanged(BR.seekDisplay);
}
Now you can bind these to the Widget
s.
<SeekBar
android:id="@+id/my_seek"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progress="@={viewModel.seekValue}" />
<TextView
android:id="@+id/my_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.seekDisplay}" />
The main point to note is that you are using two-way databinding on the SeekBar
using @={viewModel.seekValue}
, which will call your setSeekValue(...)
method.
One this is called, it will update your TextView
by calling setSeekDisplay(...)
Well you may need to use two-way binding like below.
Add in binding utils class
private static final String ANDROID_PROGRESS = "android:progress";
@BindingAdapter(ANDROID_PROGRESS)
public static void setSeekbarProgress(SeekBar seekBar, int progress) {
try {
seekBar.setProgress(progress);
} catch (Resources.NotFoundException nfe) {
nfe.printStackTrace();
}
}
@InverseBindingAdapter(attribute = ANDROID_PROGRESS)
public static int getSeekbarProgress(SeekBar seekBar) {
try {
return seekBar.getProgress();
} catch (Resources.NotFoundException nfe) {
nfe.printStackTrace();
return 0;
}
}
Add the observable in view model
var child1Age = ObservableField(1)
In the layout
<SeekBar
android:id="@+id/child1_age_sb"
style="@style/HotelChildAgeSeekBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_14"
android:progress="@={vm.child1Age}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/childs_age_label" />
<TextView
android:layout_marginTop="@dimen/spacing_6"
android:id="@+id/child1_age_value"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@+id/child1_age_sb"
app:layout_constraintLeft_toLeftOf="@id/child1_age_sb"
app:layout_constraintRight_toRightOf="@id/child1_age_sb"
android:text="@{String.valueOf(vm.child1Age)}"
android:textColor="@color/black_medium"
android:textSize="@dimen/font_14"/>
To get the TextView to update when the SeekBar changes, bind to progress change through the android:onProgressChanged
attribute. This expects a public method in the Model with the signature of SeekBarBindingAdapter.OnProgressChanged
:
View
<TextView
android:text="{@model.seekBarValue} />
<SeekBar
android:onProgressChanged="@{model.onValueChanged}" />
Model
public class Model {
public ObservableField<String> seekBarValue = new ObservableField<>("");
public void onValueChanged(SeekBar seekBar, int progresValue, boolean fromUser) {
seekBarValue.set(progresValue + "");
}
}
In general I advice you to look at the source code for all the Adapter classes made for Data Binding. If you for instance navigate to the file SeekBarBindingAdapter.java
in Android Studio (menu Navigate>File) you'll learn all the event methods you can bind to through the adapters there. This particular feature is available because Google have implemented the following adapter:
@BindingAdapter("android:onProgressChanged")
public static void setListener(SeekBar view, OnProgressChanged listener) {
setListener(view, null, null, listener);
}
@George Mount is correct, you have to add a handler in your layout xml which is defined in your Model or Handler class (whatever you call it).
Look at my answer for this question for a full fledged example:
Two way databinding with Android Databinding Library
Here's the example from that answer:
Example:
public class AmanteEditModel extends BaseObservable {
private String senhaConfirm;
@Bindable
public String getSenhaConfirm() {
return senhaConfirm;
}
public void setSenhaConfirm(String senhaConfirm) {
this.senhaConfirm = senhaConfirm;
notifyPropertyChanged(BR.senhaConfirm);
}
// Textwatcher Reference: http://developer.android.com/reference/android/text/TextWatcher.html
public TextWatcher getMyEditTextWatcher() {
return new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
// Important! Use the property setter, otherwhise the model won't be informed about the change.
setSenhaConfirm(s);
}
};
}
}
In your layout xml change EditText to this:
<EditText
android:id="@+id/amante_edit_senha_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:hint="Confirme a senha"
android:inputType="textPassword"
android:maxLines="1"
android:text="@{model.senhaConfirm}"
app:addTextChangeListener="@{model.myEditTextWatcher}"
/>
Watch for the namespace of addTextChangeListener. This method might not be available through the android: namespace, so I'm using app: here. You may also use bind: to make the binding more clear.
So don't miss to add
xmlns:app="http://schemas.android.com/apk/res-auto"
or
xmlns:bind="http://schemas.android.com/apk/res-auto"
to your XML namespaces.
This solution works for all input controls, custom included, given you provide the correct Listeners in your model.
TextWatcher Reference