android - 加载新数据时,recyclerview 顶部的项目布局突然发生变化
问题描述
我正在使用第 3 页,发现一个问题是,当在 recyclerview 上向下滚动并从下一页加载新数据时,它上面的项目的布局突然改变了。你可以看到这个 gif 图像(看看前 2 个项目,没有显示折扣布局)
向下滚动并加载新数据后,折扣布局突然显示在项目的顶部。
页面来源
class RecommendedPagingSource(
private val token: String,
private val search: String,
private val direction: String,
private val productService: ProductService
) : PagingSource<Int, Product>() {
override fun getRefreshKey(state: PagingState<Int, Product>): Int? = state.anchorPosition
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Product> = try {
val currentPage = params.key ?: FIRST_PAGE_INDEX
val response = productService.recomendedProductPaging(token, currentPage, search, direction)
val responseList = mutableListOf<Product>()
val data = response.body()?.data ?: emptyList()
responseList.addAll(data)
LoadResult.Page(
data = responseList,
prevKey = null,
nextKey = if (data.isNotEmpty()) currentPage.plus(1)
else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
companion object {
private const val FIRST_PAGE_INDEX = 0
}
}
适配器
class ReusablePagingAdapter<T : Any>(
private val context: Context
) : PagingDataAdapter<T, ReusablePagingAdapter<T>.ViewHolder>(DiffUtilCallback()) {
// utils
private var layout by Delegates.notNull<Int>()
private lateinit var staggedLayoutManager: StaggeredGridLayoutManager
// callback
private lateinit var adapterCallback: AdapterCallback<T>
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position)?.let { data ->
adapterCallback.initComponent(holder.itemView, data, position)
holder.itemView.setOnClickListener {
adapterCallback.onItemClicked(it, data, position)
}
}
}
// set layout
fun setLayout(layout: Int): ReusablePagingAdapter<T> {
this.layout = layout
return this
}
// callback
fun adapterCallback(adapterCallback: AdapterCallback<T>): ReusablePagingAdapter<T> {
this.adapterCallback = adapterCallback
return this
}
// layout manager
fun isStaggeredGridView(spanCount: Int): ReusablePagingAdapter<T> {
staggedLayoutManager =
StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL)
return this
}
// build recyclerview
fun buildStagged(recyclerView: RecyclerView): ReusablePagingAdapter<T> {
recyclerView.apply {
this.adapter = this@ReusablePagingAdapter
this.layoutManager = this@ReusablePagingAdapter.staggedLayoutManager
}
return this
}
class DiffUtilCallback<T> : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
}
}
接口适配器回调
interface AdapterCallback<T> {
// setup init component
fun initComponent(itemView: View, data: T, itemIndex: Int)
// onclick listener
fun onItemClicked(itemView: View, data: T, itemIndex: Int)
}
视图模型
class RecommendedViewModel(
private val productService: ProductService
) : ViewModel() {
fun recomendedProducts(
token: String,
search: String,
direction: String
): Flow<PagingData<Product>> = Pager(PagingConfig(12)) {
RecommendedPagingSource(token, search, direction, productService)
}.flow.cachedIn(viewModelScope)
}
活动
class RecommendedProductActivity : AppCompatActivity() {
private lateinit var binding: ActivityRecommendedProductBinding
private val viewModel: RecommendedViewModel by viewModel()
// utils
private lateinit var sharedPref: SharedPrefsUtil
private lateinit var adapter: ReusablePagingAdapter<Product>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRecommendedProductBinding.inflate(layoutInflater)
setContentView(binding.root)
// init utils
sharedPref = SharedPrefsUtil()
sharedPref.start(this, AUTH_TOKEN)
adapter = ReusablePagingAdapter(this)
// setup adapter
setupAdapter(binding.rvRecommended)
// init UI
initUI()
}
private fun initUI() {
sharedPref.get(AUTH_TOKEN)?.let { token ->
lifecycleScope.launch {
viewModel.recomendedProducts(token, "", "ASC").collect {
adapter.submitData(it)
}
}
}
with(adapter) {
// loading state
addLoadStateListener { loadState ->
// handle loading
if (loadState.refresh is LoadState.Loading) {
binding.layoutEmpty.visibility = View.GONE
binding.layoutError.visibility = View.GONE
binding.rvRecommended.visibility = View.GONE
binding.shimmerProduct.rootLayout.visibility = View.VISIBLE
} else if (loadState.append.endOfPaginationReached) {
// handle if data is empty
if (adapter.itemCount < 1) {
binding.layoutEmpty.visibility = View.VISIBLE
binding.rvRecommended.visibility = View.GONE
binding.shimmerProduct.rootLayout.visibility = View.GONE
}
} else {
// handle if data is exists
binding.layoutEmpty.visibility = View.GONE
binding.rvRecommended.visibility = View.VISIBLE
binding.shimmerProduct.rootLayout.visibility = View.GONE
// get error
val error = when {
loadState.prepend is LoadState.Error -> loadState.prepend as LoadState.Error
loadState.append is LoadState.Error -> loadState.append as LoadState.Error
loadState.refresh is LoadState.Error -> loadState.refresh as LoadState.Error
else -> null
}
error?.let {
binding.layoutEmpty.visibility = View.GONE
binding.rvRecommended.visibility = View.GONE
binding.layoutError.visibility = View.VISIBLE
}
}
}
}
}
private fun setupAdapter(recyclerView: RecyclerView) {
adapter.adapterCallback(adapterCallback)
.setLayout(R.layout.item_product)
.isStaggeredGridView(2)
.buildStagged(recyclerView)
}
private val adapterCallback = object : AdapterCallback<Product> {
override fun initComponent(itemView: View, data: Product, itemIndex: Int) {
// set utils
itemView.tv_nama_produk.text = data.name
// set image
Glide.with(this@RecommendedProductActivity)
.load(data.thumbnail?.url)
.into(itemView.image_product)
// set status makanan
val setStatus = fun(text: String, color: Int) {
itemView.tv_status.text = StringBuilder().append(text)
itemView.status.setBackgroundResource(color)
}
when (data.visible) {
"0" -> setStatus("Habis", R.drawable.status_habis)
"1" -> setStatus("Ready", R.drawable.status_ready)
}
// set discount
if (data.discount == "0")
itemView.tv_harga.text = Helpers.changeToRupiah(data.price!!.toDouble())
else {
itemView.layout_discount.visibility = View.VISIBLE
// set new price
val oldPrice = data.price!!
val discount = oldPrice * data.discount!!.toLong() / 100
val newPrice = oldPrice - discount
// set view
itemView.tv_price_before.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
itemView.tv_discount.text = StringBuilder().append("${data.discount}% OFF")
itemView.tv_price_before.text = Helpers.changeToRupiah(oldPrice.toDouble())
itemView.tv_harga.text = Helpers.changeToRupiah(newPrice.toDouble())
}
}
override fun onItemClicked(itemView: View, data: Product, itemIndex: Int) {
startActivity(
Intent(this@RecommendedProductActivity, DetailProductActivity::class.java).putExtra(
DetailProductActivity.EXTRA_ID,
data.id
)
)
}
}
}
解决方案
推荐阅读
- sql - 如何将人们分成年龄桶,出生年份(SQL)
- python - Tensorflow / Keras预测函数输出长度与输入长度不匹配
- android - Exoplayer 播放声音但不显示视频,以及在广告中显示(仅听到广告和看到“跳过”和“了解更多”按钮)
- javascript - 从元素输入填写时自动调整文本区域 - Javascript
- office365 - Office 365 MyAnalytics Full vs Workplace 分析
- android - Android:如何通过蓝牙 Garmin Watch 检索 FIT 文件
- dictionary - 在冒险游戏中绘制字典
- matlab - 我正在尝试在 Matlab 中模拟蒙蒂霍尔问题。我究竟做错了什么?
- powerbi - 总结 Power BI 中串联列(或多列)的条形图
- office365 - 确定与office365的连接是否有效,如果没有连接,则执行某些操作