kotlin Android Recycler视图拖动以重新排序未正确更新Recyler视图

a5g8bdjr  于 2022-11-25  发布在  Kotlin
关注(0)|答案(1)|浏览(160)

我正在开发一个应用程序,它有一个充满实时数据项的回收器视图,需要能够重新排序这些项。我为每个项添加了一个句柄,以便拖动它,并实现了一个基本的重新排序逻辑。我遇到的问题是,当我拖动一个项时,行为非常不寻常。很难描述,所以我在这里添加了一个视频https://streamable.com/joe50r
我将添加下面的代码,但这里有一个link to the branch on my gitlab.
这是包含它的碎片

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
        }
    }

}

下面是回收程序视图适配器。

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
            }
        }
    }
}

以下是回调帮助程序

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,但在损坏的项目中,它会调用。我怀疑这可能是问题的一部分。

2lpgd968

2lpgd9681#

这个问题的答案是痛苦的,因为它的结果是因为一行代码。
我不得不改变这个
设置具有稳定ID(true)
对此
设置具有稳定ID(假)
在我的选择跟踪器中。当废弃视图时,内部的RecyclerView代码会检查该标志,我不知道为什么,但我确信这是完全有意的,它会导致视图废弃以不同的方式处理。

相关问题