使用 ItemTouchHelper 拖动项目时 RecyclerView 滚动到顶部

Posted

技术标签:

【中文标题】使用 ItemTouchHelper 拖动项目时 RecyclerView 滚动到顶部【英文标题】:RecyclerView scrolls to top when dragging item with ItemTouchHelper 【发布时间】:2021-01-15 03:49:39 【问题描述】:

我有一个奇怪的错误,每当我开始在其中拖动项目时,我的RecyclerView 就会滚动回顶部位置。如果有任何区别,它就在ViewPager 里面。您可以在附加的 .gif 中看到行为。

编辑:

似乎RecyclerView 视图在调用notifyItemMoved 时滚动到顶部,并且它滚动的幅度与第一个视图至少部分显示在屏幕上的程度相同。

查看

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/accounts_recycler_view"
    android:layout_
    android:layout_
    android:scrollbarStyle="outsideOverlay"
    android:scrollbars="none"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    tools:listitem="@layout/view_account_list_item" />

适配器

class AccountListAdapter(
private val onAccountClickListener: OnAccountClickListener) :
ListAdapter<Account, AccountListAdapter.ViewHolder>(
    AccountDiffCallback()
) 

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 
    val inflater = LayoutInflater.from(parent.context)
    return ViewHolder(
        inflater.inflate(
            R.layout.view_account_list_item,
            parent,
            false
        )
    )


override fun getItemId(position: Int): Long 
    return getItem(position).accountId.toLong()


override fun onBindViewHolder(holder: ViewHolder, position: Int) 
    holder.bind(getItem(position), onAccountClickListener)


class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), OnItemDragged 

    fun bind(account: Account, onAccountClickListener: OnAccountClickListener) 
        itemView.account_name.text = account.name
       
        itemView.setOnClickListener 
            onAccountClickListener.onAccountClick(account)
        

    

    override fun onItemSelected() 
        itemView.setBackgroundColor(
            ContextCompat.getColor(
                itemView.context,
                R.color.background_contrast
            )
        )
    

    override fun onItemClear() 
        itemView.setBackgroundColor(
            ContextCompat.getColor(
                itemView.context,
                R.color.background
            )
        )
    



class AccountDiffCallback : DiffUtil.ItemCallback<Account>() 
    override fun areItemsTheSame(oldItem: Account, newItem: Account): Boolean 
        return oldItem.accountId == newItem.accountId
    

    override fun areContentsTheSame(oldItem: Account, newItem: Account): Boolean 
        return (oldItem.balance == newItem.balance
                && oldItem.annualReturn == newItem.annualReturn
                && oldItem.name == newItem.name)
    


interface OnAccountClickListener 
    fun onAccountClick(account: Account)


interface OnItemDragged 
    fun onItemSelected()
    fun onItemClear()

ItemTouchHelper

 private fun setupListAdapter() 

    accountListAdapter = AccountListAdapter(this)
    accountListAdapter.setHasStableIds(true)

    accounts_recycler_view.adapter = accountListAdapter
    accounts_recycler_view.addItemDecoration(
        DividerItemDecoration(
            requireContext(),
            DividerItemDecoration.VERTICAL
        )
    )


    val accountTouchHelper = ItemTouchHelper(
        object : ItemTouchHelper.SimpleCallback(
            ItemTouchHelper.UP or ItemTouchHelper.DOWN,
            0
        ) 

            override fun onSelectedChanged(
                viewHolder: RecyclerView.ViewHolder?,
                actionState: Int
            ) 
                if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) 
                    val accountViewHolder = viewHolder as AccountListAdapter.ViewHolder
                    accountViewHolder.onItemSelected()
                
                super.onSelectedChanged(viewHolder, actionState)
            

            override fun clearView(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder
            ) 
                val accountViewHolder = viewHolder as AccountListAdapter.ViewHolder
                accountViewHolder.onItemClear()
                super.clearView(recyclerView, viewHolder)
            


            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean 
                val fromPos: Int = viewHolder.adapterPosition
                val toPos: Int = target.adapterPosition
                Collections.swap(_accountList, fromPos, toPos)
                accountListAdapter.notifyItemMoved(fromPos, toPos)
                return true
            

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) 

        )

    accountTouchHelper.attachToRecyclerView(accounts_recycler_view)


【问题讨论】:

【参考方案1】:

所以问题是我的 RecyclerviewViewPager 里面,它在 ConstraintLayout 里面。视图寻呼机受到垂直限制,高度设置为 0dp,但宽度设置为 match_parent。我所需要的只是将其水平约束,宽度设置为 0dp,setHasFixedSize = trueRecyclerView

当为适配器调用notifyItemMoved 时,如果RecyclerView 是灵活的,则重绘所有项目,默认情况下聚焦第一个项目。

【讨论】:

以上是关于使用 ItemTouchHelper 拖动项目时 RecyclerView 滚动到顶部的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView ItemTouchHelper.Callback:拖动交换条件

Android开发 RecyclerView实现拖动与滑动ItemTouchHelper

Android开发 RecyclerView实现拖动与滑动ItemTouchHelper

RecyclerView借助ItemTouchHelper实现拖动和滑动删除功能

Android使用ItemTouchHelper实现RecyclerView的item拖动位置交换

Android使用ItemTouchHelper实现RecyclerView的item拖动位置交换和侧滑删除