【问题标题】:Data binding + LiveData is not working with complex nested objects数据绑定 + LiveData 不适用于复杂的嵌套对象
【发布时间】:2020-10-25 06:04:16
【问题描述】:

我在 LiveData 和数据绑定库中遇到了一些意外行为。 我已经在这个答案https://stackoverflow.com/a/48194074/13321296 中实现了 CustomLiveData,所以我可以在父类中调用 notifyChange() 来更新 UI。

我有父对象(为了简洁省略了一些方法):

class Day(val tasks: MutableList<RunningTask>,
          state: DayState = DayState.WAITING,
          var dayStartTime: Long = 0L,
          currentTaskPos: Int = 0): BaseObservable() {

    var state: DayState = state
        set(value) {
            field = value
            notifyChange()
        }

    var currentTaskPos: Int = currentTaskPos
        set(value) {
            field = value
            notifyChange()
        }

    fun start() {
        dayStartTime = System.currentTimeMillis()
        state = DayState.ACTIVE
        resetTasks()
        tasks[currentTaskPos].start()

        notifyChange()
    }
}

子对象:

class RunningTask(
    startTime: Long,

    var name: String = "",
    private val originalDuration: Long = 0L,

    val sound: String
): BaseObservable() {
    var startTime: Long = startTime
        set(value) {
            field = value
            uiStartTime = convertMillisToStringFormat(value)
        }

    @Bindable
    var uiStartTime: String = convertMillisToStringFormat(startTime)
        set(value) {
            field = value
            notifyPropertyChanged(BR.uiStartTime)
        }

    var duration: Long = originalDuration
        set(value) {
            field = value
        }

    var state: State = State.WAITING

    var progress: Long = 0L
        set(value) {
            field = value
        }

    var timePaused: Long = 0L
    var timeRemain: String = convertMillisToStringFormat(duration)

    enum class State {
        WAITING, ACTIVE, COMPLETED, DISABLED
    }

    fun start() {
        state = State.ACTIVE
    }
}

问题是当我更改 Day 的 tasks 字段内的项目时,来自 item_main_screen_task.xml 的数据绑定没有更新,例如调用方法 start(),但其他字段,例如 state,确实更新正确,所以我猜问题出在其中的列表。

fragment_main_screen.xml,recyclerview 填充了 Day 类字段 tasks

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <import type="android.view.View"/>

        <variable
            name="viewmodel"
            type="com.sillyapps.meantime.ui.mainscreen.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/tasks"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:visibility="@{viewmodel.noTemplate ? View.GONE : View.VISIBLE}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toTopOf="@+id/play_button"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/constraintLayout"
            tools:listitem="@layout/item_main_screen_task"
            tools:visibility="visible" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

item_main_screen_task,taskState 属性基本上就是 BindingAdapter,它根据 Day 的 state 枚举设置背景可绘制对象:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="task"
            type="com.sillyapps.meantime.data.RunningTask" />

        <variable
            name="taskAdapterPosition"
            type="Integer" />

        <variable
            name="clickListener"
            type="com.sillyapps.meantime.ui.ItemClickListener" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:taskState="@{task.state}"

        android:onClick="@{() -> clickListener.onClickItem(taskAdapterPosition)}">

        <TextView
            android:id="@+id/time"
            style="@style/TimeItemStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:text="@{task.uiStartTime}"

            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/enter_name"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="17:00" />

        <TextView
            android:id="@+id/enter_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"

            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"

            android:text="@{task.name}"
            app:layout_constraintBottom_toBottomOf="@+id/time"
            app:layout_constraintEnd_toStartOf="@+id/progress"
            app:layout_constraintStart_toEndOf="@+id/time"
            app:layout_constraintTop_toTopOf="@+id/time"
            tools:text="Свободное время" />

        <TextView
            android:id="@+id/progress"
            style="@style/TimeItemStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginEnd="8dp"

            android:text="@{task.timeRemain}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/enter_name"
            app:layout_constraintTop_toTopOf="@+id/time"
            tools:text="01:00" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

提前致谢。

【问题讨论】:

    标签: android android-databinding android-livedata


    【解决方案1】:

    原来解决方案非常简单,但有点出乎意料

    子类应该扩展 BaseObservable,并在每个数据绑定字段的设置器上调用 notifyChange(),类似这样:

    class RunningTask(
        startTime: Long,
    
        var name: String = "",
        private val originalDuration: Long = 0L,
    
        val sound: String
    ): BaseObservable() {
        var startTime: Long = startTime
            set(value) {
                field = value
                uiStartTime = convertMillisToStringFormat(value)
            }
    
        @Bindable
        var uiStartTime: String = convertMillisToStringFormat(startTime)
            set(value) {
                field = value
                notifyPropertyChanged(BR.uiStartTime)
            }
    
        var state: State = State.WAITING
            set(value) {
                 field = value
                 notifyChange()
            }
        
        ...
    }
    

    在提出问题之前,我似乎已经在 uiStartTime 中实现了这一点,但我只是不知道它起作用的确切原因

    【讨论】:

      猜你喜欢
      • 2014-02-02
      • 1970-01-01
      • 2019-08-27
      • 1970-01-01
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多