RecyclerView прокручивается вверх при перетаскивании элемента с помощью ItemTouchHelper

#android #android-recyclerview #android-viewpager #itemtouchhelper

#Android #android-recyclerview #android-viewpager #itemtouchhelper

Вопрос:

У меня есть эта странная ошибка, из-за которой моя RecyclerView прокрутка возвращается в верхнюю позицию всякий раз, когда я начинаю перетаскивать в нее элемент. Это внутри ViewPager , если это имеет какое-либо значение. Вы можете увидеть поведение в .gif прилагается.

Редактировать:

Кажется, что RecyclerView при вызове view прокручивается вверх notifyItemMoved , и он прокручивается так же сильно, чтобы первый вид хотя бы частично отображался на экране.

введите описание изображения здесь

Вид

 <androidx.recyclerview.widget.RecyclerView
    android:id="@ id/accounts_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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
                amp;amp; oldItem.annualReturn == newItem.annualReturn
                amp;amp; 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:

Итак, проблема заключалась в том, что мой Recyclerview был внутри ViewPager , который был внутри ConstraintLayout . Просмотр пейджера был ограничен по вертикали с высотой, установленной на 0dp, но ширина была установлена на match_parent . Все, что мне нужно было ограничить его по горизонтали шириной, равной setHasFixedSize = true 0dp, и RecyclerView

При вызове notifyItemMoved адаптера, если RecyclerView он гибкий, все элементы перерисовываются и по умолчанию фокусируются на первом элементе.

Ответ №2:

Если вы используете NestedScrollView, просто удалите и вместо использования других макетов используйте ConstraintLayout

Шаги:

Используйте ConstrainLayout и RecylerView следующим образом:

 <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@ id/rvDraggable"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_0"
            android:layout_marginStart="@dimen/dp_16"
            android:layout_marginEnd="@dimen/dp_16"
            android:scrollbarStyle="outsideOverlay"
            android:scrollbars="none"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
  

Примечание — вы также можете попробовать использовать:

  • Привязка к просмотру.recycler.setHasFixedSize(true)

  • Если вы используете notifyDataSetChanged(), просто замените его на notifyItemMoved() в методе onMove()