首页 > 解决方案 > 加载新数据时,recyclerview 顶部的项目布局突然发生变化

问题描述

我正在使用第 3 页,发现一个问题是,当在 recyclerview 上向下滚动并从下一页加载新数据时,它上面的项目的布局突然改变了。你可以看到这个 gif 图像(看看前 2 个项目,没有显示折扣布局)

GIF 图像

向下滚动并加载新数据后,折扣布局突然显示在项目的顶部。

页面来源

    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
                    )
                )
            }
     
        }
     
    }

标签: androidkotlinandroid-recyclerviewandroid-pagingandroid-diffutils

解决方案


推荐阅读