Android RecyclerView拖拽排序后视图更新异常求助
问题:RecyclerView拖拽排序行为异常(LiveData填充数据)
我正在开发一款应用,其中的RecyclerView由LiveData数据填充,需要支持列表项拖拽排序功能。我为每个列表项添加了拖拽手柄,并实现了基础的排序逻辑,但遇到了拖拽时行为异常的问题,具体表现可查看对应视频。相关代码如下,也可查看对应GitLab分支。
包含RecyclerView的Fragment代码
class PlayerListFragment : Fragment(), OnStartDragListener { private val viewModel: PlayerListFragmentViewModel by viewModel() private val adapter = PlayerListRecyclerViewAdapter(this ) private val callback: ItemTouchHelper.Callback = ItemReorderCallback(adapter) private val itemTouchHelper = ItemTouchHelper(callback) private val DEFAULT_ACTION_BAR = R.layout.player_list_fragment_custom_action_bar override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment setActionBar(DEFAULT_ACTION_BAR) return inflater.inflate(R.layout.fragment_player_list, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) observePlayers() initializeAddPlayerFabOnClick() setRecyclerViewAdapter() } private fun initializeAddPlayerFabOnClick() { val addPlayerFab = view?.findViewById<FloatingActionButton>(R.id.add_player_fab) addPlayerFab?.setOnClickListener { launchNewPlayerDialog() } } private fun setActionBar(actionBarId: Int) { val activity = requireActivity() as AppCompatActivity val supportActionBar = activity.supportActionBar supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM supportActionBar?.setCustomView(actionBarId) setDefaultActionBarListeners() } private fun setRecyclerViewAdapterItemTouchHelper() { val playersRecyclerView = requireView().findViewById<RecyclerView>(R.id.players_recyclerview) itemTouchHelper.attachToRecyclerView(playersRecyclerView) } private fun setRecyclerViewAdapter() { val playersRecyclerView = requireView().findViewById<RecyclerView>(R.id.players_recyclerview) playersRecyclerView.layoutManager = LinearLayoutManager(context) playersRecyclerView.adapter = adapter setRecyclerViewAdapterItemTouchHelper() } private fun setDefaultActionBarListeners() { val activity = requireActivity() as MainActivity val supportActionBar = activity.supportActionBar supportActionBar?.customView?.findViewById<ImageView>(R.id.edit_button)?.setOnClickListener { showDragHandles() } } private fun launchNewPlayerDialog() { val inflater = LayoutInflater.from(context) val builder = AlertDialog.Builder(context) val builderView = inflater.inflate(R.layout.fragment_add_player_dialog, null) AddPlayerAlertDialogUtilities.initializeAddPlayerDialogButtons(builder, requireContext()) builder.setView(builderView) val dialog = builder.show() AddPlayerAlertDialogUtilities.initializeAutoCompleteTextViews(dialog, resources, requireContext()) dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { if (!AddPlayerAlertDialogUtilities.hasErrors(dialog)) { createPlayer(dialog) dialog.dismiss() } } } private fun createPlayer(dialog: AlertDialog) { val player = Player( 0, AddPlayerAlertDialogUtilities.getName(dialog), AddPlayerAlertDialogUtilities.getSex(dialog), AddPlayerAlertDialogUtilities.getRace(dialog), AddPlayerAlertDialogUtilities.getClass(dialog), 0, 0 ) CoroutineScope(Dispatchers.IO).launch { viewModel.addPlayer(player) } } override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) { viewHolder?.let { itemTouchHelper.startDrag(it) } } private fun observePlayers() { viewModel.getAllPlayers().observe(viewLifecycleOwner) { adapter.setPlayers(it) } } private fun showDragHandles() { val recyclerView = requireView().findViewById<RecyclerView>(R.id.players_recyclerview) recyclerView.children.forEach { it.findViewById<ImageView>(R.id.drag_handle).visibility = View.VISIBLE } } }
RecyclerView适配器代码
class PlayerListRecyclerViewAdapter(private val onStartDragListener: OnStartDragListener) : RecyclerView.Adapter<PlayerListRecyclerViewAdapter.ViewHolder>(), ItemTouchHelperAdapter { private var players: List<Player> = ArrayList() private lateinit var recyclerView: RecyclerView var tracker: SelectionTracker<Long>? = null init { setHasStableIds(true) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.player_list_recyclerview_item, parent, false) return ViewHolder(view, onStartDragListener) } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) this.recyclerView = recyclerView } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bindItems(players[position]) } override fun getItemCount(): Int { return players.size } override fun getItemId(position: Int): Long = position.toLong() @SuppressLint("NotifyDataSetChanged") fun setPlayers(newDataSet: List<Player>) { this.players = newDataSet notifyDataSetChanged() } override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { if (fromPosition < toPosition) { for (i in fromPosition until toPosition) { Collections.swap(players, i, i + 1) } } else { for (i in fromPosition downTo toPosition + 1) { Collections.swap(players, i, i - 1) } } notifyItemMoved(fromPosition, toPosition) return true } override fun onItemDismiss(position: Int) { } inner class ViewHolder(itemView: View, private val onStartDragListener: OnStartDragListener? = null) : RecyclerView.ViewHolder(itemView) { fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> = object : ItemDetailsLookup.ItemDetails<Long>() { override fun getPosition(): Int = adapterPosition override fun getSelectionKey(): Long = itemId } fun bindItems(player: Player) { itemView.tag = player.id val sexImageView = itemView.findViewById<ImageView>(R.id.sex_imageview) val playerNameTextview = itemView.findViewById<TextView>(R.id.player_name_textview) val raceAndClassTextview = itemView.findViewById<TextView>(R.id.race_and_class_textview) val raceAndClassText = "${player.race} ${player.clazz}" playerNameTextview.text = player.name raceAndClassTextview.text = raceAndClassText if (player.sex == "Male") { sexImageView.setBackgroundResource(R.drawable.male_recycler_view_item_selector) } else { sexImageView.setBackgroundResource(R.drawable.female_recycler_view_item_selector) } setOnClickListener() setOnTouchListener() } private fun setOnClickListener() { itemView.setOnClickListener { val parentActivity = itemView.context as MainActivity val bundle = Bundle() bundle.putInt("playerId", itemView.tag as Int) parentActivity.inflateFragment(PlayerFragment(), bundle, "PLAYER_FRAGMENT") } } @SuppressLint("ClickableViewAccessibility") private fun setOnTouchListener() { val dragHandle = itemView.findViewById<ImageView>(R.id.drag_handle) dragHandle.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { onStartDragListener?.onStartDrag(this) } false } } } }
Callback辅助类代码
class ItemReorderCallback(private val adapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() { override fun isLongPressDragEnabled(): Boolean { return false } override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int { val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN return makeMovementFlags(dragFlags, 0) } override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { adapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition) return true } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { TODO("Not yet implemented") } }
我曾在另一个项目中使用完全相同的实现,功能正常。两者唯一的区别是:正常项目中拖拽排序后不会调用onBindViewHolder,而出现问题的项目会调用该方法,我怀疑这是导致异常的原因。
内容的提问来源于stack exchange,提问作者Jeremy Archer
相关产品推荐
相关产品推荐

