删除项目后,ListAdapter 不会更新 RecyclerView 中项目的索引(位置) [英] ListAdapter does not update indices (positions) of the items in the RecyclerView after deleting an item

查看:37
本文介绍了删除项目后,ListAdapter 不会更新 RecyclerView 中项目的索引(位置)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的 RecyclerView,其中包含具有自己的 PopupMenu 的项目.您可以使用该菜单删除项目.

I have a simple RecyclerView that holds items that have their own PopupMenus. You can delete an item using that menu.

现在在向您展示代码之前,先看一下问题(请同时参考屏幕截图):

Now before showing You the code, take a look at the problem (please refer to the screenshots as well):

  • 首先,我删除某个中间位置(在我的情况下 - 位置 1 或 索引 1)上的项目,
  • 然后,当尝试访问最后一个项目的 PopupMenu - 第二个屏幕截图中位置 1 的项目时,应用程序崩溃
  • 看起来好像那个项目的实际位置不是 1 而是 2,所以 索引>RecyclerView 未更新:
  • First, I delete an item on some intermediary position (in my case - position 1 or index 1),
  • Then, when trying to access the PopupMenu of the last item - the one at the position 1 at the second screenshot, the application crashes,
  • It appears as though the actual position of that item was not 1 but 2, so the indices of the items in the RecyclerView were not updated:
Process: com.sweak.teachernotebook, PID: 15271
    java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
        at androidx.core.view.ViewGroupKt.get(ViewGroup.kt:32)
        at com.sweak.teachernotebook.ui.main.fragment.NoteFragment.showOptionsMenu(NoteFragment.kt:78)
        ...

这是最相关的代码:

NoteFragment.kt

class NoteFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Getting the reference to the AndroidViewModel
        noteViewModel = ViewModelProvider(this,
            NoteViewModelFactory(requireActivity().application))
            .get(NoteViewModel::class.java)
    }

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        ...
        // Getting the reference to the RecyclerView
        noteRecyclerView = binding.recyclerViewNotes
        
        // Creating the ListAdapter for the RecyclerView
        noteAdapter = NoteAdapter(object : NoteAdapter.OptionsMenuClickListener {
            override fun onOptionsMenuClicked(position: Int) {
                // Implementation of the showOptionsMenu method below
                showOptionsMenu(position)
            }
        })
        noteRecyclerView.adapter = noteAdapter

        // Setting the observer for the ViewModel data
        noteViewModel.allNotes.observe(viewLifecycleOwner, { notes ->
            noteAdapter.submitList(notes)
        })

        ...
    }

    private fun showOptionsMenu(position: Int) {
        // Here upon trying to get the view at the position greater than the list size, the IndexOutOfBoundException occurs
        val popupMenu = PopupMenu(context, noteRecyclerView[position].findViewById(R.id.note_options))
        popupMenu.inflate(R.menu.note_options_menu)

        popupMenu.setOnMenuItemClickListener(object : PopupMenu.OnMenuItemClickListener {
            override fun onMenuItemClick(item: MenuItem?): Boolean {
                when (item?.itemId) {
                    R.id.delete -> {
                        // Here the deletion of an item happens
                        noteViewModel.delete(noteAdapter.getNoteAt(position))
                        return true
                    }
                    R.id.edit -> {
                        // Editing the note...
                    }
                }
                return false
            }
        })

        popupMenu.show()
    }
}

NoteAdapter.kt

class NoteAdapter(
    private var optionsMenuClickListener: OptionsMenuClickListener
) : ListAdapter<Note, NoteAdapter.NoteHolder>(DIFF_CALLBACK) {
    
    class NoteHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var noteTitle: TextView = itemView.findViewById(R.id.note_title)
        var noteDescription: TextView = itemView.findViewById(R.id.note_description)
        var noteOptions: TextView = itemView.findViewById(R.id.note_options)
    }

    interface OptionsMenuClickListener {
        fun onOptionsMenuClicked(position: Int)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteHolder {
        ...
    }

    override fun onBindViewHolder(holder: NoteHolder, position: Int) {
        val currentNote: Note = getItem(position)
        holder.noteTitle.text = currentNote.title
        holder.noteDescription.text = currentNote.description

        // What happens upon clicking the menu button on a single item
        holder.noteOptions.setOnClickListener {
            // The action specified in the implementation of the OptionsMenuClickListener is executed
            // with the parameter @position provided while binding the NoteHolder with the item on the specified position
            optionsMenuClickListener.onOptionsMenuClicked(position)
        }
    }

    companion object {
        // Callback for the ListAdapter to evaluate the differences between the items
        private val DIFF_CALLBACK: DiffUtil.ItemCallback<Note> =
            object : DiffUtil.ItemCallback<Note>() {
                override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
                    return oldItem.id == newItem.id
                }
                override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
                    return (oldItem.title == newItem.title) and
                            (oldItem.description == newItem.description)
                }
            }
    }
}

NoteViewModel.kt

class NoteViewModel(application: Application) : AndroidViewModel(application) {
    
    // We get the reference to the data that we are working on (allNotes)
    // to observe it (since it is LiveData) in the NoteFragment.kt
    private val repository: NoteRepository = NoteRepository(application)
    val allNotes: LiveData<List<Note>> = repository.allNotes

    ...

    fun delete(note: Note) {
        repository.delete(note)
    }
}

我尝试在使用 noteAdapter.notifyDataSetChanged() 删除项目后重新加载数据集,但没有成功.

I've tried to reload the dataset after deleting an item using noteAdapter.notifyDataSetChanged() but it didn't work.

从那时起,我就无法想出任何解决此问题的方法.

Since then I couldn't come up with any solution to this problem.

我将不胜感激!

在这种情况下我可以问的另一种问题是如何刷新 NoteAdapter 以便项目的索引重置为正常(例如 0, 1, 2 而不是 0, 1, 3).也许现在有人会有答案了.

Another kind of question I could ask in this situation would be on how to refresh the NoteAdapter so the indices of the items are reset to normal (e.g. 0, 1, 2 instead of 0, 1, 3). Maybe now someone will have an answer.

推荐答案

问题出在NoteAdapteronBindViewHolder方法上.

解决问题的更改是将参数 position 的用法更改为 holder.absoluteAdapterPosition,因为根据 文档:

The change that resolves the issue is changing the usage of the parameter position to holder.absoluteAdapterPosition because according to the documentation:

有时,您可能需要获取确切的适配器位置才能响应用户事件执行某些操作.在这种情况下,您应该使用此方法来计算 ViewHolder 的 Adapter 位置.

Sometimes, you may need to get the exact adapter position to do some actions in response to user events. In that case, you should use this method which will calculate the Adapter position of the ViewHolder.

问题中修改后的正确代码现在看起来像这样:

The modified and correct code from the question now looks like this:

override fun onBindViewHolder(holder: NoteHolder, position: Int) {
        val currentNote: Note = getItem(holder.absoluteAdapterPosition)
        holder.noteTitle.text = currentNote.title
        holder.noteDescription.text = currentNote.description

        holder.noteOptions.setOnClickListener {
            optionsMenuClickListener.onOptionsMenuClicked(holder.absoluteAdapterPosition)
        }
    }

这篇关于删除项目后,ListAdapter 不会更新 RecyclerView 中项目的索引(位置)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆