In my application I want use recyclerview with DiffUtils into recyclerview adapter.
When I scroll between the items, the items get messed up and moved.
I should added this code holder.setIsRecyclable(false) into onBindViewHolder to fix this problem!
But I don't want disable recyclerview mode!
My codes:

class ReservedListAdapter @Inject constructor() : RecyclerView.Adapter<ReservedListAdapter.ViewHolder>() {

    private lateinit var binding: ItemReservedListBinding
    private lateinit var context: Context
    private var moviesList = emptyList<Result>()
    private val userToken by lazy { GoodPrefs.getInstance().getString(USER_TOKEN, "") }
    private var selectedType = ""

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        binding = ItemReservedListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        context = parent.context
        return ViewHolder()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(moviesList[position])
        holder.setIsRecyclable(false)
    }

    override fun getItemCount() = moviesList.size

    //override fun getItemId(position: Int) = position.toLong()

    inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {

        @SuppressLint("SetTextI18n", "NotifyDataSetChanged", "CheckResult")
        fun bind(item: Result) {
            binding.apply {
                //Title
                var type = ""
                item.counseling?.let { counseling ->
                    type = if (counseling.type == PHONE) {
                        context.getString(R.string.byPhone)
                    } else {
                        context.getString(R.string.textual)
                    }
                    titleTxt.text = "${context.getString(R.string.visit)} $type - " +
                            context.getString(R.string.doctor) +
                            "${counseling.physician?.firstName} ${counseling.physician?.lastName}"
                }
                //Date
                if (item.startAt != null) {
                    val date = item.startAt.split(" ")[0]
                    val hour = item.startAt.split(" ")[1]
                    val showDate = if (item.counseling?.type == PHONE) {
                        "${date.convertDateToFarsiWithMonthName()} | " + "${context.getString(R.string.hour)} ${hour.dropLast(3)}"
                    } else {
                        "${date.convertDateToFarsiWithMonthName()} | ${context.getString(R.string.duringDay)}"
                    }
                    dateTxt.text = showDate
                }
                //Avatar
                if (item.counseling?.physician?.profile?.files != null) {
                    if (item.counseling.physician.profile.files.isNotEmpty()) {
                        item.counseling.physician.profile.files.forEach { file ->
                            if (file?.description == AVATAR) {
                                avatarLoading.visibility = View.VISIBLE
                                ApiClient.getInstance().apisUseCase().getAvatarImage(userToken, file.id!!)
                                    .applyIoScheduler()
                                    .subscribe({
                                        avatarLoading.visibility = View.GONE
                                        if (it.isSuccessful) {
                                            if (it.code() == 200) {
                                                if (it.body() != null) {
                                                    val decodedBytes: ByteArray =
                                                        Base64.decode(it.body()!!.data!!.file, Base64.DEFAULT)
                                                    avatarImg.load(decodedBytes)
                                                }
                                            }
                                        }
                                    }, {
                                        avatarLoading.visibility = View.GONE
                                    })
                            } else {
                                avatarImg.load(R.drawable.avatar_doctor)
                            }
                        }
                    }
                }
                //Status
                infoTxt.text = item.status
                infoTxt.setCorner(10)
                //Click
                binding.root.setOnClickListener {
                    notifyDataSetChanged()
                    onItemClickListener?.let {
                        it(item, selectedType)
                    }
                }
            }
        }
    }

    private fun dynamicallyStatusColor(textColor: Int, bgColor: Int) {
        binding.infoTxt.apply {
            setTextColor(ContextCompat.getColor(context, textColor))
            setBgColor(ContextCompat.getColor(context, bgColor))
        }
    }

    private var onItemClickListener: ((Result, String) -> Unit)? = null

    fun setOnItemClickListener(listener: (Result, String) -> Unit) {
        onItemClickListener = listener
    }

    fun setData(data: List<Result>) {
        val moviesDiffUtil = BaseDiffUtils(moviesList, data)
        val diffUtils = DiffUtil.calculateDiff(moviesDiffUtil)
        moviesList = data
        diffUtils.dispatchUpdatesTo(this)
    }
}

BaseDiffUtils codes:

class BaseDiffUtils<T>(private val oldItem: List<T>, private val newItem: List<T>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldItem.size
    }

    override fun getNewListSize(): Int {
        return newItem.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItem[oldItemPosition] === newItem[newItemPosition]
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItem[oldItemPosition] === newItem[newItemPosition]
    }
}

我如何解决这个问题?

推荐答案

您应该从适配器中删除字段"BINDING",它应该是ViewHolder的一个属性,因 for each ViewHolder都引用它自己的View.相反,将其作为构造函数参数传递给ViewHolder也没有必要将ViewHolder设置为"内部",因为它是一个独立的实体.

EDIT:

class ReservedListAdapter @Inject constructor() : RecyclerView.Adapter<ReservedListAdapter.ViewHolder>() {

    private var moviesList = emptyList<Result>()
    private val userToken by lazy { GoodPrefs.getInstance().getString(USER_TOKEN, "") }
    private var selectedType = ""

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemReservedListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(moviesList[position])
    }

    override fun getItemCount() = moviesList.size

    //override fun getItemId(position: Int) = position.toLong()

    private class ViewHolder(private val binding: ItemReservedListBinding) : RecyclerView.ViewHolder(binding) {

        @SuppressLint("SetTextI18n", "NotifyDataSetChanged", "CheckResult")
        fun bind(item: Result) {
            binding.apply {
                //Title
                var type = ""
                item.counseling?.let { counseling ->
                    type = if (counseling.type == PHONE) {
                        context.getString(R.string.byPhone)
                    } else {
                        context.getString(R.string.textual)
                    }
                    titleTxt.text = "${context.getString(R.string.visit)} $type - " +
                            context.getString(R.string.doctor) +
                            "${counseling.physician?.firstName} ${counseling.physician?.lastName}"
                }
                //Date
                if (item.startAt != null) {
                    val date = item.startAt.split(" ")[0]
                    val hour = item.startAt.split(" ")[1]
                    val showDate = if (item.counseling?.type == PHONE) {
                        "${date.convertDateToFarsiWithMonthName()} | " + "${context.getString(R.string.hour)} ${hour.dropLast(3)}"
                    } else {
                        "${date.convertDateToFarsiWithMonthName()} | ${context.getString(R.string.duringDay)}"
                    }
                    dateTxt.text = showDate
                }
                //Avatar
                if (item.counseling?.physician?.profile?.files != null) {
                    if (item.counseling.physician.profile.files.isNotEmpty()) {
                        item.counseling.physician.profile.files.forEach { file ->
                            if (file?.description == AVATAR) {
                                avatarLoading.visibility = View.VISIBLE
                                ApiClient.getInstance().apisUseCase().getAvatarImage(userToken, file.id!!)
                                    .applyIoScheduler()
                                    .subscribe({
                                        avatarLoading.visibility = View.GONE
                                        if (it.isSuccessful) {
                                            if (it.code() == 200) {
                                                if (it.body() != null) {
                                                    val decodedBytes: ByteArray =
                                                        Base64.decode(it.body()!!.data!!.file, Base64.DEFAULT)
                                                    avatarImg.load(decodedBytes)
                                                }
                                            }
                                        }
                                    }, {
                                        avatarLoading.visibility = View.GONE
                                    })
                            } else {
                                avatarImg.load(R.drawable.avatar_doctor)
                            }
                        }
                    }
                }
                //Status
                infoTxt.text = item.status
                infoTxt.setCorner(10)
                //Click
                binding.root.setOnClickListener {
                    notifyDataSetChanged()
                    onItemClickListener?.let {
                        it(item, selectedType)
                    }
                }
            }
        }
    }

    private fun dynamicallyStatusColor(textColor: Int, bgColor: Int) {
        binding.infoTxt.apply {
            setTextColor(ContextCompat.getColor(context, textColor))
            setBgColor(ContextCompat.getColor(context, bgColor))
        }
    }

    private var onItemClickListener: ((Result, String) -> Unit)? = null

    fun setOnItemClickListener(listener: (Result, String) -> Unit) {
        onItemClickListener = listener
    }

    fun setData(data: List<Result>) {
        val moviesDiffUtil = BaseDiffUtils(moviesList, data)
        val diffUtils = DiffUtil.calculateDiff(moviesDiffUtil)
        moviesList = data
        diffUtils.dispatchUpdatesTo(this)
    }
}

Android相关问答推荐

在Kotlin Jetpack Compose中点击按钮后启动另一个Android应用程序

如何在Reaction Native中显示Google Map iFrame?

约束布局:垂直链中的视图应将内容包裹到空间的1/3

Kotlin DSL:为什么我可以从Play Store获取发布版本的日志(log)?

如何解决Gradle构建错误:java.lang.NoSuchMethodError

升级到 Jetpack Compose 物料 list 2023.08.00 需要我将 targetSdk 更改为 34

如何在jetpack compose中使可组合的屏幕zoom 到不同的手机(屏幕)尺寸?

如何在Android Studio中删除项目

Android 应用程序从 Android Studio 安装,但不是作为 .apk 在外部安装.抛出java.lang.UnsatisfiedLinkError

Android:使用依赖项 ViewModelProviderFactory 初始化 ViewModel 的正确方法

如何将可重用的 ExtendedFloatingActionButton 与可重用的脚手架链接起来

如何使用 Jetpack Compose 制作两个圆圈

如何在 Android 应用程序未激活/未聚焦时显示视图?

android xml底部空间大

Jetpack Compose Material3 禁用 ListItem

IconButton 中可绘制的图标是黑色的,尽管它是白色的

单击登录按钮后从应用程序中退出

Android Compose webview 被拉伸

Android全屏AlertDialog

基线配置文件 x R8/Proguard