Использование CalendarView с привязкой данных

Я хочу использовать двустороннюю привязку данных с компонентами Android LiveData (в качестве альтернативы наблюдаемым полям. Вот код для простого проекта с CalendarView и EditText, который отображает обе информации при нажатии кнопки.

<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
    <variable
        name="testDate"
        type="android.arch.lifecycle.MutableLiveData&lt;Long&gt;" />
    <variable
        name="testString"
        type="android.arch.lifecycle.MutableLiveData&lt;String&gt;" />
</data>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onButtonClick"
        android:text="Show data"/>

    <CalendarView
        android:id="@+id/cal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:date="@={testDate}"/>

    <EditText
        android:id="@+id/str"
        android:text="@={testString}"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
</layout>

И код активности:

class MainActivity : AppCompatActivity() {

val liveDate = MutableLiveData<Long>().apply { value = System.currentTimeMillis() }
val liveString = MutableLiveData<String>().apply { value = "Date: " }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
            .also {
                it.testDate = liveDate
                it.testString = liveString

                it.cal.setOnDateChangeListener { view, year, month, dayOfMonth ->
                    Toast.makeText(applicationContext, "${view.date}", Toast.LENGTH_SHORT).show()
                }

            }
}

@TargetApi(Build.VERSION_CODES.O)
fun onButtonClick(view: View) {
    Toast.makeText(this, liveString.value +
            Instant.ofEpochMilli(liveDate.value!!).atZone(ZoneId.systemDefault()).toLocalDate()
            , Toast.LENGTH_SHORT)
            .show()
}
}

Двусторонняя привязка отлично работает для String, но не для даты. Я нашел этот пост , в котором говорится, что выбранная дата на самом деле отличается от даты, используемой android:date... Достаточно честно, вы можете зафиксировать это действие по изменению даты в прослушивателе. Проблема в том, что прослушиватель setOnDateChangeListener (примененный в also {} выше) вообще не запускается, когда есть настройка двусторонней привязки.

Поправьте меня, если я ошибаюсь, но если я хочу получить выбранную дату, я должен использовать OnDateChangeListener. Он также кажется несовместимым с android:date@={...}, потому что использование двусторонней привязки переопределяет наш слушатель. Было бы разумно, если бы android:date@={...} обеспечивал ту же функциональность, что и OnDateChangeListener, но это не так.

Итак, последний вопрос: можно ли как-то получить выбранную дату с помощью двусторонней привязки данных?


person Luke    schedule 18.09.2018    source источник
comment
Привет, Люк, ты проверил мой ответ?   -  person Khemraj Sharma    schedule 21.09.2018


Ответы (1)


Есть куча багов, которые я нашел после отслеживания всех классов.

Ошибка 1

Это ошибка документации Android. См. CalendarViewBindingAdapter.

Вы можете видеть, что они создали адаптер привязки для android:date, но нет @InverseBindingAdapter.

@BindingAdapter({"android:date"})
public static void setDate(CalendarView view, long date) {
    if (view.getDate() != date) {
        view.setDate(date);
    }
}

// no @InverseBindingAdapter written

Но в документации написано, что CalendarView поддерживает два- способ привязки.

Возможно, мы получим это в следующих обновлениях.

Я также пытался добавить @InverseBindingAdapter, но это тоже не сработало.

@InverseBindingAdapter(attribute = "android:date", event = "android:dateAttrChanged")
public static long getDateLong(CalendarView view) {
    return view.getDate();
}

Ошибка 2

Попробуйте установить setOnDateChangeListener на CalendarView, вы всегда будете получать одну и ту же дату.

Ниже не работает

binding.cal.setOnDateChangeListener((view, year, month, dayOfMonth) -> {
     Log.d(TAG, "aLong: " + new Date(view.getDate()).toString());
});

Ниже работает

binding.cal.setOnDateChangeListener((view, year, month, dayOfMonth) -> {
    Log.d(TAG, "aLong: " + new Date(year, month, dayOfMonth).toString());
});

Вот почему мой @InverseBindingAdapter не работает.

Потому что calendarView.getDate() не дает правильную дату.

Исправить

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

public class CalendarViewBindingAdapter {
    @BindingAdapter(value = {"android:onSelectedDayChange", "android:dateAttrChanged"},
            requireAll = false)
    public static void setListeners(CalendarView view, final CalendarView.OnDateChangeListener onDayChange,
                                    final InverseBindingListener attrChange) {
        if (attrChange == null) {
            view.setOnDateChangeListener(onDayChange);
        } else {
            view.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
                @Override
                public void onSelectedDayChange(CalendarView view, int year, int month,
                                                int dayOfMonth) {
                    if (onDayChange != null) {
                        onDayChange.onSelectedDayChange(view, year, month, dayOfMonth);
                    }
                    Calendar instance = Calendar.getInstance();
                    instance.set(year, month, dayOfMonth);
                    view.setDate(instance.getTimeInMillis());
                    attrChange.onChange();
                }
            });
        }
    }
}

Что я исправил

Я просто установил дату в CalendarView (view.setDate()), которая раньше была 0.

person Khemraj Sharma    schedule 19.09.2018
comment
Таким образом, мы должны реализовать что-то, что должно быть нестандартным, поскольку привязка для android:data бесполезна. Я отказался от двухсторонней привязки и просто использовал прослушиватель для создания даты из года/месяца/дня. Спасибо за публикацию. - person Luke; 21.09.2018
comment
Да, привязка данных используется для удобства, если мы сталкиваемся с каким-то багом, то у нас тоже старое оружие. :) - person Khemraj Sharma; 21.09.2018