我正在使用带有流的 Paging 3 来填充我的 RecyclerView,我单击一个项目并转到详细信息页面并为该项目投票,这会将 1 添加到 ViewHolder 中可见的计数器。
一切都正确填充,当我从详细信息页面返回主屏幕时,我已经刷新,但是当使用数据更新时,该项目和其他一些项目会闪烁。
我的问题是,当我更新时,我赞成的项目会完全闪烁,通常使用 recylerview 适配器我会将稳定的 ID 设置为 true,但是你不能使用 PagingDataAdapter 来做到这一点,我认为这可能是使用 Glide 但整个 ViewHolder 会刷新,我列表中的其他一些也会刷新。
我的 PagingDataAdapter 的 DiffUtil 使用 objects.equals,我在我的条目项中覆盖了它,只有在没有用户输入的情况下不太可能随机更改的字段,所以我认为这不是问题。(虽然,在我看来,其他刷新的项目可能意味着这可能是错误的?
观察者 XML -
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="entry"
type="com.model.Entry" />
</data>
<androidx.cardview.widget.CardView 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="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:cardCornerRadius="@dimen/rounded_corners"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_entry_item"
android:layout_width="160dp"
android:layout_height="180dp"
android:scaleType="centerCrop"
app:image_from_url="@{entry.imageUrl}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_votes"
style="@style/Text.RH.Body1.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_medium"
android:paddingEnd="@dimen/padding_xsmall"
android:paddingBottom="@dimen/padding_medium"
android:text="@{String.valueOf(entry.voteCount)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/img_entry_item"
tools:text="3000" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_votes_suffix"
style="@style/Text.RH.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/none"
android:paddingEnd="@dimen/padding_medium"
android:text="@string/votes"
app:layout_constraintBaseline_toBaselineOf="@id/text_votes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/text_votes"
app:layout_constraintTop_toBottomOf="@id/img_entry_item" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_index"
style="@style/Text.RH.Body1.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/item_label_background"
android:padding="@dimen/padding_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
我的图像加载绑定适配器
@BindingAdapter("image_from_url")
fun AppCompatImageView.loadImageWithGlide(url: String?) {
try {
if(this.tag == url) return
this.tag = url
when {
url.isNullOrBlank() -> this.setImageDrawable(null)
else -> {
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.transition(DrawableTransitionOptions.withCrossFade())
.placeholder(CircularProgressDrawable(this.context).apply {
strokeWidth = 5f
centerRadius = 30f
start()
})
.into(this)
}
}
} catch (ex: Exception) {
Timber.e(ex, "Unable to load rounded image url: $url")
}
}
这是使用 PagingDataAdapter 从我的 ViewHolder 填充的
class EntryAdapter(
private val maxItems: Int? = null,
private val onEntryClicked: (entry: Entry) -> (Unit),
) : PagingDataAdapter<Entry, EntryAdapter.ViewHolder>(
DiffItemCallback()
) {
override fun getItemCount(): Int = maxItems?.let { super.getItemCount().coerceAtMost(maxItems) } ?: super.getItemCount()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
ItemEntryItemVotesBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(
entry = getItem(position),
adapterPosition = position
)
}
inner class ViewHolder(
private val binding: ItemEntryItemVotesBinding
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
getItem(absoluteAdapterPosition)?.let(onEntryClicked)
}
}
fun bind(entry: Entry?, adapterPosition: Int) {
binding.entry = entry
binding.textIndex.text = (adapterPosition + 1).toString()
}
}
}
使用 Flows 从我的片段中填充
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initDataBinding()
initEntriesRecyclerView()
}
...
private fun initEntriesRecyclerView() {
binding.rvEntries.apply {
adapter = rvAdapter
setSnapToStart()
}
viewModel.competition.observe(
viewLifecycleOwner
) { competition ->
competition?.let {
lifecycleScope.launchWhenResumed {
viewModel.getEntries(
competitionId = competition.id
).collectLatest {
if (entryAdapter.itemCount == 0) {
binding.rvAdapter.layoutAnimation = AnimationUtils.loadLayoutAnimation(
requireContext(),
R.anim.layout_animation_from_right
)
}
rvAdapter.submitData(it)
}
}
}
}
}
和我的视图模型
fun getEntries(competitionId: Long) = entryRepository.getEntries(
competitionId = competitionId,
sortBy = EntrySortBy.Votes,
sortDirection = EntrySortDirection.Desc,
scope = viewModelScope
)
然后存储库
override fun getEntries(
competitionId: Long,
sortBy: EntrySortBy,
sortDirection: EntrySortDirection,
scope: CoroutineScope
) = Pager(
PagingConfig(pageSize = 20)
) {
EntryDataSource(
competitionId = competitionId,
sortBy = sortBy,
sortDirection = sortDirection,
entryRepository = this
)
}.flow.cachedIn(scope)