Как выполнить двустороннюю привязку данных к ToggleButton?

У меня есть класс ObservableBoolean в моем классе активности, который привязан к атрибуту "checked" моего ToggleButton так:

android:checked="@{activity.editing}"

Я надеялся, что это создаст двустороннюю связь между кнопкой и булевым, но только переносит изменения с поля на кнопку, а не наоборот. Что я делаю неправильно, или это не в рамках Android DataBinding?

Ответ 1

Вам нужно другое "=", чтобы сообщить Android, что вы хотите использовать двухстороннюю привязку данных:

android:checked="@={activity.editing}"

Вы можете найти более подробную информацию об этом в статье Wordpress Джорджа Гора:

Двусторонняя привязка данных

Android невосприимчив к типичному вводу данных, и часто важно отражать изменения от пользователей, вводимых обратно в модель. Например, если приведенные выше данные были в форме контакта, было бы неплохо, если бы отредактированный текст был возвращен в модель без необходимости извлекать данные из EditText. Вот как вы это делаете:

<layout ...>
    <data>
        <variable type="com.example.myapp.User" name="user"/>
    </data>
    <RelativeLayout ...>
        <EditText android:text="@={user.firstName}" .../>
    </RelativeLayout>
</layout>

Довольно изящный, а? Единственное отличие здесь состоит в том, что выражение помечено как @={} вместо @{}. Ожидается, что большинство привязок данных будет оставаться односторонним, и мы не хотим, чтобы все эти слушатели создали и наблюдали за изменениями, которые никогда не произойдут.

Ответ 2

Не нужно принимать ObservableBoolean, вы можете выполнить эту операцию с помощью регулярного метода getter-setter для булевой переменной. Подобно этому в вашем классе модели

public class User{
   private boolean checked;

   public boolean isChecked() {
       return checked;
   }

   public void setChecked(boolean checked) {
       this.checked = checked;
   }
}

выполнить двустороннюю привязку на ToggleButton.

<ToggleButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="@={user.checked}"/>

и выберите это значение, используя переменную привязки.

binding.getUser().isChecked()

Ответ 3

Вот простой пример двухсторонней привязки данных с использованием коммутатора, который также имеет свойство Checked, например ToggleButton.

Item.java:

import android.databinding.BaseObservable;
import android.databinding.Bindable;

public class Item extends BaseObservable {
    private Boolean checked;
    @Bindable
    public Boolean getChecked() {
        return this.checked;
    }
    public void setChecked(Boolean checked) {
        this.checked = checked;
        notifyPropertyChanged(BR.checked);
    }
}

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    public Item item;
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        item = new Item();
        item.setChecked(true);

        /* By default, a Binding class will be generated based on the name of the layout file,
        converting it to Pascal case and suffixing "Binding" to it.
        The above layout file was activity_main.xml so the generate class was ActivityMainBinding */

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setItem(item);
    }

    public void button_onClick(View v) {
        item.setChecked(!item.getChecked());
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="item"
            type="com.example.abc.twowaydatabinding.Item" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Switch
            android:id="@+id/switch_test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="@={item.checked}" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change"
            android:onClick="button_onClick"/>

    </LinearLayout>
</layout>

build.gradle:

android {
...
    dataBinding{
        enabled=true
    }

}

Исходная документация: https://developer.android.com/topic/libraries/data-binding/index.html

Ответ 4

Ниже приведены способы установки OnCheckedChangeListener в привязке данных:

(1) Установить методом выражения

В макете

<variable
    name="activity"
    type="com.innovanathinklabs.sample.activities.CalendarActivity"/>

<ToggleButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="@={model.checked}"
    android:onCheckedChanged="@{activity::onGenderChanged}"
    />

В действии

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityCalendarBinding>(this, R.layout.activity_calendar)
        binding.activity = this
        binding.model = Model()
    }

    fun onGenderChanged(buttonView: CompoundButton, isChecked: Boolean) {
        println("buttonView = [$buttonView], isChecked = [$isChecked]")
    }
}

(2) Устанавливается по выражению лямбда и вызову метода

<variable
    name="model"
    type="com.innovanathinklabs.sample.data.Model"/>

<variable
    name="activity"
    type="com.innovanathinklabs.sample.activities.HomeActivity"/>

<ToggleButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="@={model.checked}"
    android:onCheckedChanged="@{(button, bool)-> activity.saveGender(bool)}"
    />

В действии

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityCalendarBinding>(this, R.layout.activity_calendar)
        binding.activity = this
        binding.model = Model()
    }

    fun saveGender(isChecked: Boolean) {
        println("isChecked = [$isChecked]")
    }
}

(3) Пропустить анонимный класс OnCheckedChangeListener для макета

<variable
    name="onGenderChange"
    type="android.widget.CompoundButton.OnCheckedChangeListener"/>

<ToggleButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="@={model.checked}"
    android:onCheckedChanged="@{onGenderChange}"
    />

В действии

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityCalendarBinding>(this, R.layout.activity_calendar)
        binding.model = Model()
        binding.setOnGenderChange { buttonView, isChecked ->
            println("buttonView = [$buttonView], isChecked = [$isChecked]")
        }
    }
}

(4) Пропустить OnCheckedChangeListener по ссылке

<variable
    name="onGenderChange2"
    type="android.widget.CompoundButton.OnCheckedChangeListener"/>

<ToggleButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="@={model.checked}"
    android:onCheckedChanged="@{onGenderChange2}"
    />

Деятельность

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityCalendarBinding>(this, R.layout.activity_calendar)
        binding.model = Model()
        binding.onGenderChange2 = onGenderChange
    }

    private val onGenderChange: CompoundButton.OnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
        println("buttonView = [$buttonView], isChecked = [$isChecked]")
    }
}

Это никогда не будет работать

Потому что вы не можете установить два обратных вызова на один компонент. Один обратный вызов уже установлен двусторонней привязкой, поэтому ваш обратный вызов не будет работать.

binding.toggleButton.setOnCheckedChangeListener { buttonView, isChecked ->
    println("buttonView = [$buttonView], isChecked = [$isChecked]")
}

Проверьте класс CompoundButtonBindingAdapter, чтобы узнать, как работает переключение перекрестных ссылок.