How to bind method on RadioGroup on checkChanged event with data-binding

旧巷老猫 提交于 2019-12-02 20:59:16
kirtan403

After digging to the bunch of methods, I found this question on SO which helped me understand how to bind single methods of listeners.

Here is what to do with RadioGroup:

In RadioGroup listener you have a method onCheckedChanged(RadioGroup g, int id). So you can directly bound that method to your handler or your activity by passing an instance of it as a variable in layout file and calling a method with the same signature.

So call on layout file like this:

  <RadioGroup
        android:id="@+id/split_type_radio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:checkedButton="@+id/split_type_equal"
        android:gravity="center"
        android:onCheckedChanged="@{handler.onSplitTypeChanged}"
        android:orientation="horizontal">

       ...

   </RadioGroup>

And in my activity or handler, I need to simply provide the method with same name and signature like this:

public void onSplitTypeChanged(RadioGroup radioGroup,int id) {
  // ...
}

Just make sure method is public.

NOTE: This works for any (most of, I have not tried all) listener methods. Like for EditText you can provide android:onTextChanged and so on.

I am using a string, and in this case I have bindable based on viewModel.getCommuteType() viewModel.setCommuteType(String)

<RadioGroup
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.DRIVING)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.DRIVING)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="D"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.BICYCLE)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.BICYCLE)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="B"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.WALKING)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.WALKING)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="W"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.BUS)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.BUS)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="T"/>

After some hours I found easy way: two-way databinding in android. It's base skeleton with and Kotlin. Also you can use ObservableField()

  1. Set your to data
  2. Create your with buttons as you like. Important: set all radio buttons id !!!
  3. Set in your radio group two-way to checked variable (use viewmodel variable)
  4. Enjoy)

layout.xml

<data>
    <variable
        name="VM"
        type="...YourViewModel" />
</data>


<LinearLayout
            android:id="@+id/settings_block_env"
            ...
            >


            <RadioGroup

                android:id="@+id/env_radioGroup"
                android:checkedButton="@={VM.radio_checked}">

                <RadioButton
                    android:id="@+id/your_id1"/>

                <RadioButton
                   android:id="@+id/your_id2" />

                <RadioButton
                    android:id="@+id/your_id3"/>

                <RadioButton
                    android:id="@+id/your_id4"/>
            </RadioGroup>

        </LinearLayout>

class YourViewModel(): ViewModel {

var radio_checked = MutableLiveData<Int>()


init{
    radio_checked.postValue(R.id.your_id1)//def value
}

//other code
}

Often you care more about what was actually checked instead of "something was checked". In such case alternative solution is to ignore RadioGroup and bind all items as below:

<RadioGroup (...) >
        <RadioButton (...)
            android:checked="@={viewModel.optionA}"/>

        <RadioButton (...)
            android:checked="@={viewModel.optionB}"/>

        <RadioButton (...)
            android:checked="@={viewModel.optionC}"/>
</RadioGroup>

where optionA, optionB and optionC are defined in ViewModel like below:

public final ObservableBoolean optionA = new ObservableBoolean();
public final ObservableBoolean optionB = new ObservableBoolean();
public final ObservableBoolean optionC = new ObservableBoolean();

This is usually enough, however if you want to react immediately on click then you can add callBacks and use them like that:

OnPropertyChangedCallback userChoosedA = new OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        (...) // basically propertyId can be ignored in such case
    }
};

optionA.addOnPropertyChangedCallback(userChoosedA);

Advantage of such approach is that you don't need to compare and track "id".

In my current project, I did it like this. I have three currency in the project and I choose one of them via RadioGroup.

It's enum with currencies:

enum class Currency(val value: Byte) {
    USD(0),
    EUR(1),
    RUB(2);

    companion object Create {
        fun from(sourceValue: Byte): Currency = values().first { it.value == sourceValue }
        fun from(sourceValue: String): Currency = values().first { it.toString() == sourceValue }
    }
}

A piece of my ViewModel:

    class BaseCurrencyViewModel : ViewModelBase<BaseCurrencyModelInterface>() {
        /**
         * Selected currency
         */
        val currency: MutableLiveData<Currency> = MutableLiveData()

        /**
         *
         */
        init {
            currency.value = Currency.USD   // Init value
        }
  }

Part of my layout (pay attention to binding in RadioGroup and tags of RadioButton):

<RadioGroup
    android:id="@+id/currencySwitchers"

    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

    app:selectedCurrency = "@{viewModel.currency}"

    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintEnd_toEndOf="parent">

    <RadioButton
        android:id="@+id/usdSwitcher"

        android:text="USD"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="USD"
    />

    <RadioButton
        android:id="@+id/eurSwitcher"

        android:text="EUR"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="EUR"
    />

    <RadioButton
        android:id="@+id/rubSwitcher"

        android:text="RUB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="RUB"
    />
</RadioGroup>

And the final part - binding adapter.

@BindingAdapter("selectedCurrency")
fun setSelectedCurrency(view: View, value: MutableLiveData<Currency>?) {
    view.getParentActivity()?.let { parentActivity ->
        value?.observe(parentActivity, Observer { value ->
            view.findViewWithTag<RadioButton>(value.toString())
                ?.also {
                    if(!it.isChecked) {
                        it.isChecked = true
                    }
                }
            }
        )

        (view as RadioGroup).setOnCheckedChangeListener { radioGroup, checkedId ->
            val currency = Currency.from(radioGroup.findViewById<RadioButton>(checkedId).tag as String)
            if(value != null && value.value != currency) {
                value.value = currency
            }
        }
    }
}

In this way, I got two-way binding between RadioGroup and a property in my ViewModel.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!