You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:45:35