1

解决了

RV - 回收站视图

我在警报对话框中有一个 RV。RV 适配器使用 DiffUtil.ItemCallback 扩展 ListAdapter。适配器列表每 500 毫秒使用倒数计时器更新一次(检查列表项是否已下载)。

问题是,列表已更新并使用新数据提交给适配器,但列表项视图并未根据提供的新数据进行更新,如下所示。我正在使用数据/视图绑定来更新列表项视图。

RV 有时会在滚动时更新项目视图。

PS:RV 是 NestedScrollView 的孩子

这就是它现在的工作方式

适配器代码

class AlarmSongsAdapter(
    private val onItemClicked: (AlarmSongItem) -> Unit,
    private val startDownloading: (String) -> Unit,
    private val insertDownloadEntityInDB: (DownloadEntity) -> Unit
) : ListAdapter<AlarmSongItem, AlarmSongsAdapter.AlarmSongsViewHolder>(DiffUtilCallback) {

object DiffUtilCallback : DiffUtil.ItemCallback<AlarmSongItem>() {
    override fun areItemsTheSame(oldItem: AlarmSongItem, newItem: AlarmSongItem): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: AlarmSongItem, newItem: AlarmSongItem): Boolean {
        return oldItem == newItem
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AlarmSongsViewHolder {
    return AlarmSongsViewHolder(AlarmsSongListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), onItemClicked, startDownloading, insertDownloadEntityInDB)
}

override fun onBindViewHolder(holder: AlarmSongsViewHolder, position: Int) {
    holder.bind(getItem(position))
}

class AlarmSongsViewHolder(
    private val binding: AlarmsSongListItemBinding,
    private val onItemClicked: (AlarmSongItem) -> Unit,
    private val startDownloading: (String) -> Unit,
    private val insertDownloadEntityInDB: (DownloadEntity) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(alarmSongItem: AlarmSongItem) {
        binding.alarmSongItem = alarmSongItem
        binding.executePendingBindings()
    }

    init {
        binding.downloadButton.setOnClickListener {
            val alarmSongItem = binding.alarmSongItem!!
            when(alarmSongItem.downloadState){
                Download.STATE_STOPPED -> {
                    startDownloading(alarmSongItem.audioFile)
                    val storageInfo = StorageUtils.currentStorageTypeAndPath(binding.root.context)
                    insertDownloadEntityInDB(alarmSongItem.toDownloadEntity(storageInfo))
                }
                else -> {}
            }
        }

        binding.root.setOnClickListener {
            onItemClicked(binding.alarmSongItem!!)
        }
    }
}
}

列表项查看代码

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

<data>
    <variable
        name="alarmSongItem"
        type="com.baja.app.domain.models.AlarmSongItem" />
</data>

<com.google.android.material.card.MaterialCardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    app:cardElevation="5dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp">

        <androidx.cardview.widget.CardView
            android:id="@+id/song_item_thumbnail_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardBackgroundColor="@android:color/transparent"
            app:cardCornerRadius="6dp"
            app:cardElevation="0dp">

            <ImageView
                android:id="@+id/song_item_thumbnail"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_centerVertical="true"
                android:scaleType="centerCrop"
                app:srcCompat="@drawable/bg_default_light"
                tools:ignore="ContentDescription"
                app:thumbnailFromUri="@{alarmSongItem.thumbnail}" />

        </androidx.cardview.widget.CardView>

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:id="@+id/download_progress_container"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true">

            <ImageView
                android:id="@+id/download_bg"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:scaleType="centerCrop"
                app:srcCompat="?bg_default_circular"
                tools:ignore="ContentDescription"
                android:layout_centerInParent="true" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/download_button"
                style="@style/AppTheme.OutlinedButton.Icon"
                android:layout_width="32dp"
                android:layout_height="32dp"
                app:cornerRadius="32dp"
                app:icon="@drawable/ic_download"
                app:iconTint="@android:color/white"
                changeIcon="@{alarmSongItem.downloadState}"
                android:layout_centerInParent="true" />

            <com.google.android.material.progressindicator.ProgressIndicator
                android:id="@+id/download_progress_bar"
                style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
                android:layout_width="33dp"
                android:layout_height="33dp"
                app:circularRadius="17dp"
                app:indicatorColor="?attr/progressIndicatorColor"
                app:indicatorWidth="1dp"
                showProgressBar="@{alarmSongItem.downloadState}"
                android:layout_centerInParent="true"
                android:visibility="gone" />

        </RelativeLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginStart="20dp"
            android:layout_toEndOf="@id/song_item_thumbnail_container"
            android:orientation="vertical"
            android:weightSum="2"
            android:layout_toStartOf="@id/download_progress_container"
            android:layout_marginEnd="8dp">

            <TextView
                android:id="@+id/song_item_name"
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:ellipsize="end"
                android:gravity="bottom"
                android:maxLines="1"
                android:textSize="16sp"
                android:textStyle="bold"
                tools:text="Sa re ga ma pa"
                android:text="@{alarmSongItem.title}" />


            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/song_item_artist"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_marginEnd="4dp"
                    android:ellipsize="end"
                    android:gravity="center_vertical"
                    android:maxWidth="150dp"
                    android:maxLines="1"
                    android:textSize="14sp"
                    tools:text="Sidharth Arun"
                    android:text="@{alarmSongItem.artist}" />

                <View
                    android:layout_width="5dp"
                    android:layout_height="5dp"
                    android:layout_gravity="center_vertical"
                    android:background="@drawable/dot" />

                <TextView
                    android:id="@+id/song_item_duration"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_marginStart="4dp"
                    android:ellipsize="end"
                    android:gravity="center_vertical"
                    android:maxLines="1"
                    tools:text="10:12"
                    app:formatDuration="@{alarmSongItem.duration}" />

            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>

</com.google.android.material.card.MaterialCardView>

绑定适配器函数

@BindingAdapter("thumbnailFromUri")
fun thumbnailFromUri(view: ImageView, uri: String) {
    Glide.with(view).load(uri).placeholder(R.drawable.bg_default_light).error(R.drawable.bg_default_light).into(view)
}

@BindingAdapter("changeIcon")
fun changeIconBasedOnDownloadState(view: MaterialButton, state: Int) {
    when (state) {
        Download.STATE_COMPLETED -> view.setIconResource(R.drawable.ic_check)
        else -> view.setIconResource(R.drawable.ic_download)
    }
}

@BindingAdapter("showProgressBar")
fun showProgressbarBasedOnState(view: ProgressIndicator, state: Int) {
    when (state) {
        Download.STATE_QUEUED,
        Download.STATE_RESTARTING,
        Download.STATE_DOWNLOADING -> view.visibility = View.VISIBLE
        else -> view.visibility = View.GONE
    }
}
4

6 回答 6

2

该视频对查明问题非常有帮助。

您的 Diffutils“areContentsTheSame()”正在检查项目而不是项目的单个属性。下载文件后,您需要让“areContentsTheSame()”检查下载属性以判断特定属性是否发生了变化。

例子

class MyDiffCallback : DiffUtil.ItemCallback<Dev>() {
    ... 

    override fun areContentsTheSame(oldItem: Dev, newItem: Dev): Boolean {
        return oldItem.downloadStatus == newItem.download.status && 
        oldItem == newItem
    }
}
于 2020-11-23T05:10:02.157 回答
2

问题是,列表已更新并使用新数据提交给适配器,但列表项视图并未根据提供的新数据进行更新,如下所示。我正在使用数据/视图绑定来更新列表项视图。

发生这种情况是因为您正在向您提交相同的列表,您submitList()可以查看这篇文章以获取更多信息

我最近遇到了同样的问题,我已经能够通过使用很容易地解决它onBindViewHolder(holder: AlarmSongsViewHolder, position: Int, payloads: MutableList<Any>)

在您的 DiffUtilCallback 中:

const val BUNDLE_TIME = "bundle_time"
object DiffUtilCallback : DiffUtil.ItemCallback<AlarmSongItem>() {
    override fun areItemsTheSame(oldItem: AlarmSongItem, newItem: AlarmSongItem): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: AlarmSongItem, newItem: AlarmSongItem): Boolean = false

    // This will be called every time you submit a list (so every 500ms)
    override fun getChangePayload(oldItem: AlarmSongItem, newItem: AlarmSongItem): Any {
        val diffBundle = Bundle()

        // pass the data you want to update
        diffBundle.putLong(BUNDLE_TIME, newItem.time)

        return diffBundle
    }
}

然后在您的适配器覆盖中: 注意最后有 payloads:MutableList

override fun onBindViewHolder(holder: AlarmSongsViewHolder, position: Int, payloads: MutableList<Any>) {
    if(payloads.isEmpty()) {
        // if empty it's a new item that appears on the screen
        super.onBindViewHolder(holder, position, payloads)
        return
    }
    payloads.forEach { when(it) {
        is Bundle -> {
            val time = it.getLong(BUNDLE_TIME)
            holder.binding.alarmSongItem.time.text = time.toString()
        }
    }}
}

ViewHolder如果您不想公开,您甚至可以在您的函数中创建一个函数来传递数据以进行更新binding

class AlarmSongsViewHolder(
    private val binding: AlarmsSongListItemBinding,
    private val onItemClicked: (AlarmSongItem) -> Unit,
    private val startDownloading: (String) -> Unit,
    private val insertDownloadEntityInDB: (DownloadEntity) -> Unit
) : RecyclerView.ViewHolder(binding.root) {

    fun bind(alarmSongItem: AlarmSongItem) {
        binding.alarmSongItem = alarmSongItem
        binding.executePendingBindings()
    }

    fun updateMyItem(time: Long) {
        binding.alarmSongItem.time.text = time.toString()
    }
}
于 2020-11-23T09:46:18.550 回答
0

删除changeIconBasedOnDownloadState并将代码放入bind(). 假设在新列表AlarmSongItem.downloadState中有不同的,这就是你需要做的。

将您的绑定适配器代码移动到bind()

fun bind(alarmSongItem: AlarmSongItem) {
    binding.alarmSongItem = alarmSongItem
    binding.executePendingBindings()

     when (alarmSongItem.downloadState) {
        Download.STATE_QUEUED,
        Download.STATE_RESTARTING,
        Download.STATE_DOWNLOADING -> view.visibility = View.VISIBLE
        else -> view.visibility = View.GONE
    }
}
于 2020-11-21T14:36:01.203 回答
0

您的问题:更新数据时 ViewHolder 未更新

有三种方法可以触发 ViewHolder 重新加载内容:

  1. 您将其滚动出屏幕并返回 -> 因为它是一个 recyclerView,它会将 ViewHolder 重用于另一个 Item,当您向上滚动时,它将重新加载第一个项目 -> 更新了 img
  2. 当您通知适配器某个项目已更改时
  3. 使用 DiffUtils 并通过调用重新加载 ViewHolderdiffResult.dispatchUpdatesTo(adapter)

第一个选项似乎对您有用!

对于第二个:
如果您调用adapter.notifyDataSetChanged()它将重新加载所有 ViewHolders。
如果您调用adapter.notifyItemChanged(int position)它将重新加载特定的项目位置。

要了解问题的根源,您可能想尝试一下,看看问题是否更深。

对于第三个:
请显示计算 DiffUtil 结果的代码。

于 2020-11-21T18:36:41.403 回答
0

您需要向您的适配器添加一个方法,该方法将在列表更新时调用

class AlarmSongsAdapter(alarmSongItems: List<AlarmSongItem>) : RecyclerView.Adapter<AlarmSongsAdapter.ViewHolder>() {

    private val mAlarmSongItems = mutableListOf<AlarmSongItem>()

    init {
        mAlarmSongItems.addAll(alarmSongItems)
    }

    fun swap(alarmSongItems: List<AlarmSongItem>) {
            val diffCallback = DiffUtilCallback(this.mAlarmSongItems, alarmSongItems)
            val diffResult = DiffUtil.calculateDiff(diffCallback)
    
            this.mAlarmSongItems.clear()
            this.mAlarmSongItems.addAll(alarmSongItems)
            diffResult.dispatchUpdatesTo(this)
        }
}

mAlarmSongItems 是您传递给适配器的初始列表(抱歉,我没有复制您的所有变量,只是显示差异有意义的变量)

您的回电,

class DiffUtilCallback(
    private val oldList: List<AlarmSongItem>,
    private val newList: List<AlarmSongItem>
) : DiffUtil.Callback() {

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
    }

}

因此,现在无论您在哪里收到更新的倒数计时器值,在哪里初始化适配器,您都可以这样做

alarmAdapter.swap(updatedAlarmValues)
于 2020-11-23T02:12:00.823 回答
0

我已经解决了这个问题(以我的方式)。感谢所有答案,它们真的很有帮助,但在我的用例中没有。

解决方案:

为了使其准确地工作,我在我的 rv_list_item.xml 中为下载详细信息(如状态、百分比等)创建了另一个变量,并通过了相应的下载。

DiffUtil 现在运行良好。

于 2020-11-26T06:54:16.547 回答