我正在开发一个应用程序,它有一个充满实时数据项的回收器视图,需要能够重新排序这些项。我为每个项添加了一个句柄,以便拖动它,并实现了一个基本的重新排序逻辑。我遇到的问题是,当我拖动一个项时,行为非常不寻常。很难描述,所以我在这里添加了一个视频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,但在损坏的项目中,它会调用。我怀疑这可能是问题的一部分。
1条答案
按热度按时间2lpgd9681#
这个问题的答案是痛苦的,因为它的结果是因为一行代码。
我不得不改变这个
设置具有稳定ID(true)
对此
设置具有稳定ID(假)
在我的选择跟踪器中。当废弃视图时,内部的RecyclerView代码会检查该标志,我不知道为什么,但我确信这是完全有意的,它会导致视图废弃以不同的方式处理。