RecyclerView onCreateViewHolder中bindingAdapterPosition返回-1
RecyclerView中bindingAdapterPosition返回-1问题排查
问题现象
实现RecyclerView评论列表功能时,在onCreateViewHolder方法内调用holder.bindingAdapterPosition始终返回-1,无法获取当前条目位置。日志校验显示AsyncListDiffer持有的列表数据已正常加载,但位置取值持续失败。
相关实现代码
Adapter与ViewHolder核心代码
class CommentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var likeClick: LikeClick? = null private var disLikeClick: DisLikeClick? = null inner class CommentHolder(view: View) : RecyclerView.ViewHolder(view) { val nicknameTextView: TextView = itemView.findViewById(R.id.nickname_text_view) val commentTextView: TextView = itemView.findViewById(R.id.comment_text_view) val likeCount: TextView = itemView.findViewById(R.id.like_count_text_view) val disLikeCount: TextView = itemView.findViewById(R.id.dislike_count_text_view) val likeBtn: ImageButton = itemView.findViewById(R.id.like_image_btn) val disLikeBtn: ImageButton = itemView.findViewById(R.id.disLike_Image_btn) } fun setLikeClickListener(clickListener: LikeClick) { likeClick = clickListener } fun setDisLikeClickListener(clickListener: DisLikeClick) { disLikeClick = clickListener } private val diffCallback = object : DiffUtil.ItemCallback<Comment>() { override fun areItemsTheSame(oldItem: Comment, newItem: Comment): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Comment, newItem: Comment): Boolean { return oldItem.id == newItem.id } } private val diff = AsyncListDiffer(this, diffCallback) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_comment, parent, false) val holder: RecyclerView.ViewHolder = CommentHolder(view) //set on click listener for like and dislike Log.d("TESTLOG",diff.currentList.toString()) // 问题代码位置 val commentItem = diff.currentList[holder.bindingAdapterPosition] val likeBtn=view.findViewById<ImageButton>(R.id.like_image_btn) val dislikeBtn=view.findViewById<ImageButton>(R.id.disLike_Image_btn) likeBtn.setOnClickListener { likeClick?.onLikeClick(commentItem.id,likeBtn) } dislikeBtn.setOnClickListener { disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn) } return holder } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val commentItem = diff.currentList[position] val videoHolder = holder as CommentHolder videoHolder.apply { commentTextView.text = commentItem.comment nicknameTextView.text = commentItem.nickname likeCount.text = commentItem.commentlike.toString() disLikeCount.text = commentItem.dislike.toString() //check if user already liked/disliked this comment and change icon based on that Utils.likes.forEach { if(it == commentItem.id){ likeBtn.isSelected=true } } Utils.disLikes.forEach { if(it==commentItem.id){ disLikeBtn.isSelected=true } } } } override fun getItemCount(): Int { return diff.currentList.size } fun submitList(list: List<Comment>) { diff.submitList(list) } interface LikeClick { fun onLikeClick(commentId: Int, likeBtn:ImageButton) } interface DisLikeClick { fun onDisLikeClick(commentId: Int, disLikeBtn:ImageButton) } }
日志打印结果
onCreateViewHolder中打印的列表数据正常:
D/TESTLOG: [Comment(comment=firstcomment , commentlike=0, createdAt=2022-07-06T04:35:43.000Z, dislike=0, id=4, nickname=Alpac, videoId=170c3d0c-331f-4da9-974b-39703940f819)]
Adapter绑定逻辑
页面层提交数据、绑定Adapter的代码如下:
ChikiCommentsFetcher().fetchCommentsOfAVideo(videoId).observe(viewLifecycleOwner){ commentsRecyclerView.apply { commentAdapter.submitList(it) commentAdapter.setLikeClickListener(this@CommentsFragment) commentAdapter.setDisLikeClickListener(this@CommentsFragment) adapter=commentAdapter } }
问题根因
bindingAdapterPosition返回-1是API设计的正常表现:
- 该方法的返回值只有当ViewHolder完成绑定、被附加到RecyclerView并分配了确定的列表位置时才是有效索引,未绑定状态下固定返回
RecyclerView.NO_POSITION,也就是常量值-1 onCreateViewHolder的执行时机是RecyclerView需要创建新的ViewHolder实例时,此时刚实例化的ViewHolder还未和任何数据位置绑定,也没有附加到RecyclerView布局中,自然拿不到有效位置- 原有写法还存在隐藏bug:在
onCreateViewHolder中提前获取commentItem并存在点击监听的闭包里,后续ViewHolder滑动复用时,点击事件会一直持有创建时的旧数据,导致点赞点踩传错评论ID - 额外问题:
DiffUtil的areContentsTheSame实现仅对比了评论ID,没有对比点赞数、评论内容等实际展示字段,会导致内容变化时列表不会自动刷新UI
修复方案
- 不要在
onCreateViewHolder中尝试获取位置、读取对应条目数据,这里仅做视图初始化、点击监听器设置即可 - 点击事件触发时,实时调用
holder.bindingAdapterPosition获取当前位置,同时校验位置有效性,避免NO_POSITION导致的数组越界崩溃 - 所有和数据相关的绑定逻辑,全部放在
onBindViewHolder中执行 - 补全
areContentsTheSame的字段对比逻辑
修复后的onCreateViewHolder代码参考:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_comment, parent, false) val holder = CommentHolder(view) val likeBtn=view.findViewById<ImageButton>(R.id.like_image_btn) val dislikeBtn=view.findViewById<ImageButton>(R.id.disLike_Image_btn) likeBtn.setOnClickListener { // 点击时实时获取最新位置 val currentPos = holder.bindingAdapterPosition if (currentPos != RecyclerView.NO_POSITION) { val commentItem = diff.currentList[currentPos] likeClick?.onLikeClick(commentItem.id,likeBtn) } } dislikeBtn.setOnClickListener { val currentPos = holder.bindingAdapterPosition if (currentPos != RecyclerView.NO_POSITION) { val commentItem = diff.currentList[currentPos] disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn) } } return holder }
修复后的diffCallback参考:
private val diffCallback = object : DiffUtil.ItemCallback<Comment>() { override fun areItemsTheSame(oldItem: Comment, newItem: Comment): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Comment, newItem: Comment): Boolean { // 对比所有会影响UI展示的字段 return oldItem.id == newItem.id && oldItem.comment == newItem.comment && oldItem.nickname == newItem.nickname && oldItem.commentlike == newItem.commentlike && oldItem.dislike == newItem.dislike } }
内容的提问来源于stack exchange,提问作者Abed Sako
相关产品推荐
相关产品推荐

