我创建了一个适配器(使用DiffUtil.ItemCallback扩展ListAdapter)。它是一个具有多个itemViewType的普通适配器,但如果API发送标志并且数据集大小大于1,则它应该是smth循环适配器(通过重写getItemCount()在conditions == true时返回1000)。当我通过应用设置更改应用区域设置时,我的片段会重新创建,数据会异步加载(被动地,一行中多次,来自不同的请求,取决于几个rx字段,这导致数据集在区域设置更改后立即成为不同语言的数据组合(最终所有数据集都被正确翻译)(由于特性的特殊性,使其更像是同步的是不可能的)),将其值发布到LiveData,这将触发回收器视图的更新,问题出现了:
在最后一个数据集更新后,一些视图(最接近当前显示的视图和当前显示的视图)似乎未被翻译。
发布到LiveData的最终数据集被正确翻译,它的id中甚至有正确的locale标记。同样,在视图被回收并返回到它们之后-它们也是正确的。DiffUtil的计算也是正确的(我试过在项目回调中只返回false,但回收器视图仍然没有正确更新它的视图持有者)。当itemCount == list.size时,一切正常。当适配器假装是循环的并且itemCount == 1000时-否。有人能解释一下这个行为并帮助找出解决这个问题的方法吗?
适配器代码示例:
private const val TYPE_0 = 0
private const val TYPE_1 = 1
class CyclicAdapter(
val onClickedCallback: (id: String) -> Unit,
val onCloseClickedCallback: (id: String) -> Unit,
) : ListAdapter<IViewData, RecyclerView.ViewHolder>(DataDiffCallback()) {
var isCyclic: Boolean = false
set(value) {
if (field != value) {
field = value
}
}
override fun getItemCount(): Int {
return if (isCyclic) {
AdapterUtils.MAX_ITEMS // 1000
} else {
currentList.size
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_0 -> Type0.from(parent)
TYPE_1 -> Type1.from(parent)
else -> throw ClassCastException("View Holder for ${viewType} is not specified")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is Type0 -> {
val item = getItem(
AdapterUtils.actualPosition(
position,
currentList.size
)
) as ViewData.Type0
holder.setData(item, onClickedCallback)
}
is Type1 -> {
val item = getItem(
AdapterUtils.actualPosition(
position,
currentList.size
)
) as ViewData.Type1
holder.setData(item, onClickedCallback, onCloseClickedCallback)
}
}
}
override fun getItemViewType(position: Int): Int {
return when (val item = getItem(AdapterUtils.actualPosition(position, currentList.size))) {
is ViewData.Type0 -> TYPE_0
is ViewData.Type1 -> TYPE_1
else -> throw ClassCastException("View Type for ${item.javaClass} is not specified")
}
}
class Type0 private constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun setData(
viewData: ViewData.Type0,
onClickedCallback: (id: String) -> Unit
) {
(itemView as Type0View).apply {
acceptData(viewData)
setOnClickedCallback { url ->
onClickedCallback(viewData.id,)
}
}
}
companion object {
fun from(parent: ViewGroup): Type0 {
val view = Type0View(parent.context).apply {
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
}
return Type0(view)
}
}
}
class Type1 private constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun setData(
viewData: ViewData.Type1,
onClickedCallback: (id: String) -> Unit,
onCloseClickedCallback: (id: String) -> Unit
) {
(itemView as Type1View).apply {
acceptData(viewData)
setOnClickedCallback { url ->
onClickedCallback(viewData.id)
}
setOnCloseClickedCallback(onCloseClickedCallback)
}
}
companion object {
fun from(parent: ViewGroup): Type1 {
val view = Type1View(parent.context).apply {
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
}
return Type1(view)
}
}
}
}
查看寻呼机代码示例:
class CyclicViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
ICyclicViewPager {
private val cyclicViewPager: ViewPager2
private lateinit var onClickedCallback: (id: String) -> Unit
private lateinit var onCloseClickedCallback: (id: String) -> Unit
private lateinit var adapter: CyclicAdapter
init {
LayoutInflater
.from(context)
.inflate(R.layout.v_cyclic_view_pager, this, true)
cyclicViewPager = findViewById(R.id.cyclic_view_pager)
(cyclicViewPager.getChildAt(0) as RecyclerView).apply {
addItemDecoration(SpacingDecorator().apply {
dpBetweenItems = 12
})
clipToPadding = false
clipChildren = false
overScrollMode = RecyclerView.OVER_SCROLL_NEVER
}
cyclicViewPager.offscreenPageLimit = 3
}
override fun initialize(
onClickedCallback: (id: String) -> Unit,
onCloseClickedCallback: (id: String) -> Unit
) {
this.onClickedCallback = onClickedCallback
this.onCloseClickedCallback = onCloseClickedCallback
adapter = CyclicAdapter(
onClickedCallback,
onCloseClickedCallback,
).apply {
stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
cyclicViewPager.adapter = adapter
}
override fun setState(viewPagerState: CyclicViewPagerState) {
when (viewPagerState.cyclicityState) {
is CyclicViewPagerState.CyclicityState.Enabled -> {
adapter.submitList(viewPagerState.pages) {
adapter.isCyclic = true
cyclicViewPager.post {
cyclicViewPager.setCurrentItem(
// Setting view pager item to +- 500
AdapterUtils.getCyclicInitialPosition(
adapter.currentList.size
), false
)
}
}
}
is CyclicViewPagerState.CyclicityState.Disabled -> {
if (viewPagerState.pages.size == 1 && adapter.isCyclic) {
cyclicViewPager.setCurrentItem(0, false)
adapter.isCyclic = false
}
adapter.submitList(viewPagerState.pages)
}
}
}
}
适配器实用程序代码:
object AdapterUtils {
const val MAX_ITEMS = 1000
fun actualPosition(position: Int, listSize: Int): Int {
return if (listSize == 0) {
0
} else {
(position + listSize) % listSize
}
}
fun getCyclicInitialPosition(listSize: Int): Int {
return if (listSize > 0) {
MAX_ITEMS / 2 - ((MAX_ITEMS / 2) % listSize)
} else {
0
}
}
}
已尝试不使用RecycleView的默认itemView变量(变得更糟)。尝试让diff utils总是返回false,以检查它是否正确计算了diff(是,正确)尝试将区域设置标记添加到数据集项目的ID(未帮助解决)在设置新数据之前尝试在区域设置更改时发布空数据集(我真惭愧,根本不应该考虑这个问题)尝试在rx中添加去抖动,让它在更新前等待一段时间(没有帮助)
**UPD:**当我手动调用adapter.notifyDatasetChanged()时(这不是首选方法),一切都很好,所以问题是为什么ListAdapter在我的情况下不能正确地调度通知回调?
1条答案
按热度按时间whitzsjs1#
ListAdapter
的问题在于,它没有清楚地说明您需要提供一个新列表才能使其正常工作。换句话说,the documentation says:(我引用源代码):
关键词是新列表。
但是,正如您在那里看到的,适配器所做的一切就是服从
DiffUtil
并在那里调用submitList
。因此,当您对
AsyncListDiffer
执行look at the actual source code时,您会注意到它在其代码块的开头执行了以下操作:换句话说,如果新列表(引用)与旧列表相同,无论其内容如何,都不要执行任何操作。
这听起来可能很酷,但这意味着如果您有此代码,适配器将不会真正更新:
(伪...)
原因是list1与适配器具有相同的引用,因此diffect过早退出,并且不向适配器分派任何更改。
我知道,很不起眼。
正如许多SO回答中指出的,解决方案是创建列表本身的副本。
大多数用户都这样做
对
toMutableList()
的调用创建了一个包含list1
项的新列表,因此上面的if (newList == mList) {
比较现在应该为false,应该执行正常代码。更新
请记住,很多开发人员犯的错误...
这是行不通的,因为你创建的新列表引用了 * 适配器拥有的相同对象 *。这意味着两个列表
list
和list.toList()
指向相同的东西,尽管它们是ArrayList的两个示例。但是副作用是DiffUtil
比较项,它们是相同的,所以也没有diff被分派给适配器。正确的顺序是...