方向更改后 RecyclerView 滑动功能中断

RecyclerView swipe functionality breaks after orientation change

我创建了一个基于数据库调用刷新其列表的 RecyclerView。每行都有一个选项菜单,当用户滑动时会显示该菜单。我最初的问题是 方向更改后,滑动手势不再显示菜单。我用 onCreateViewHolder()onSwipe() 命中了我所有预期的断点。但是,滑动后该行仍为 HIDE_MENU 视图类型。

所以我尝试引入LiveData来持久化方向改变后列表的状态。 RecyclerView 仍然创建并填充了项目,但现在滑动手势使应用程序崩溃并出现错误:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

我是否需要使用 LiveData 来解决在方向更改后保留我的滑动功能的原始问题?如果不是,请有人解释为什么项目视图类型在方向更改后不再更新。

如果我确实需要使用 ViewModel,我在做什么会导致列表适配器无法接收更新的列表?

历史片段

class HistoryFragment : Fragment() {
    private val historyViewModel by activityViewModels<HistoryViewModel>()

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.fragment_history, container, false)
        historyViewModel.getHistoryList().observe(viewLifecycleOwner, {
            refreshRecyclerView(it)
        })
        return root
    }
    
    private fun updateHistoryList() {
        val dbHandler = MySQLLiteDBHandler(requireContext(), null)
        val historyList = dbHandler.getHistoryList() as MutableList<HistoryObject>
        historyViewModel.setHistoryList(historyList)
    }

    private fun refreshRecyclerView(historyList: MutableList<HistoryObject>) {
        val historyListAdapter = HistoryListAdapter(historyList)
        val callback = HistorySwipeHelper(historyListAdapter)
        val helper = ItemTouchHelper(callback)
        history_list.adapter = historyListAdapter
        helper.attachToRecyclerView(history_list)
    }
    
    private fun setupSort() {
        val sortSpinner: Spinner = history_list_controls_sort
        sortSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {}
            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
                updateHistoryList()
            }
        }
    }

    override fun onViewCreated(
        view: View,
        savedInstanceState: Bundle?
    ) {
        setupSort()
    }
}

历史列表适配器

const val SHOW_MENU = 1
const val HIDE_MENU = 2

class HistoryListAdapter(private var historyData: MutableList<HistoryObject>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == SHOW_MENU) {
            val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_menu, parent, false)
            MenuViewHolder(inflatedView)
        } else {
            val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_description, parent, false)
            HistoryItemViewHolder(inflatedView)
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (historyData[position].showMenu) {
            SHOW_MENU
        } else {
            HIDE_MENU
        }
    }

    override fun getItemCount(): Int {
        return historyData.count()
    }

    fun showMenu(position: Int) {
        historyData.forEachIndexed { idx, it ->
            if (it.showMenu) {
                it.showMenu = false
                notifyItemChanged(idx)
            }
        }
        historyData[position].showMenu = true
        notifyItemChanged(position)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item: HistoryObject = historyData[position]
        if (holder is HistoryItemViewHolder) {
            holder.bindItem(item)
            ...
        }

        if (holder is MenuViewHolder) {
            holder.bindItem(item)
            ...
        }
    }

    class HistoryItemViewHolder(v: View, private val clickHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
        private var view: View = v
        private var item: HistoryObject? = null

        fun bindItem(item: HistoryObject) {
            this.item = item
            ...
        }
    }

    class MenuViewHolder(v: View, private val deleteHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
        private var view: View = v
        private var item: HistoryObject? = null

        fun bindItem(item: HistoryObject) {
            this.item = item
            ...
        }
    }
}

HistorySwipeHelper

class HistorySwipeHelper(private val adapter: HistoryListAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { return false }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        adapter.showMenu(viewHolder.adapterPosition)
    }

    override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
        return 0.1f
    }
}

HistoryViewModel

class HistoryViewModel(private var historyListHandle: SavedStateHandle) : ViewModel() {
    fun getHistoryList(): LiveData<MutableList<HistoryObject>> {
        return historyListHandle.getLiveData(HISTORY_LIST_KEY)
    }

    fun setHistoryList(newHistoryList: MutableList<HistoryObject>) {
        historyListHandle.set(HISTORY_LIST_KEY, newHistoryList)
    }

    companion object {
        const val HISTORY_LIST_KEY = "MY_HISTORY_LIST"
    }
}

Activity

class MainActivity : AppCompatActivity() {
    private val historyViewModel: HistoryViewModel by lazy {
        ViewModelProvider(this).get(HistoryViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        historyViewModel.setHistoryList(mutableListOf())
    }
}

提前致谢。如果这个问题太宽泛我可以再试一次分解。

您不应该在每次更新历史列表时都创建新的适配器。继续使用相同的适配器,只需更新项目并调用 notifyDataSetChanged() 来更新状态(当然您可以使用不同的方法来通知 insertion/deletion/etc,但首先使其与 notifyDataSetChanged() 一起使用).

我很确定这会解决问题。