Как получить страницу, которая будет выбрана в ближайшее время при настройке функции привязки в RecyclerView и ViewPager

Фон

ViewPager привязывается к виду после выполнения прокрутки, и поэтому может RecyclerView, если вы используете что-то вроде этого:

LinearSnapHelper().attachToRecyclerView(recyclerView)

Или с помощью библиотеки для привязки к определенному краю, как показано на этой библиотеке. Это может подражать ViewPager почти полностью, если вы хотите, как показано в этой библиотеке, упомянутой CommonsWare здесь.

Они оба могут функционировать одинаково, если это необходимо (привязка к одному представлению, опционально занимая все доступное пространство), поэтому этот вопрос касается их обоих.

ViewPager имеет некоторые проблемы с перезапуском представлений, но их можно исправить, например, с помощью этой библиотеки.

Эта проблема

Я должен обновить пользовательский интерфейс вновь показанного представления, как только RecyclerView/ViewPager собирается бездействовать при прокрутке.

Конечно, поскольку пользователь все еще может его трогать, было бы сложно узнать, что должно произойти, поэтому я должен также блокировать события касания, когда он устанавливает состояние прокрутки.

Это означает, например, что когда пользователь перебрасывает одну страницу, со страницы 0 на страницу 1, как только начинается привязка, сенсорные события блокируются, и я могу знать, что он привязывается к странице 1, и обновлять это стр.

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

Что я пробовал

За ViewPager Адаптер имеет только setPrimaryItem, поэтому, к сожалению, он сообщает мне, какой элемент выбран после его завершения. У меня есть функция addOnPageChangeListener, которая сообщает мне о позиции прокрутки в любой момент времени (используя onPageScrolled), но она не говорит мне, в каком направлении я иду (влево / вправо), и я не могу знать, какие данные и какой viewHolder из этого собирается быть выбранным. addOnPageChangeListener также обеспечивает состояние прокрутки (в режиме ожидания, урегулирования, перетаскивания).

За RecyclerView Я могу получить обратный вызов состояния прокрутки, когда оно собирается и когда оно становится бездействующим, но я не вижу, как получить элемент, на котором оно собирается рассчитаться.

Что касается блокировки сенсорных событий, я думаю, что я мог бы поместить интерактивное представление (которое не имеет содержимого, поэтому оно невидимо для пользователя) поверх него, когда оно устанавливается, и скрыть его (установить видимость на GONE) когда он простаивает, но мне интересно, есть ли лучший способ.

Я пытался с помощью setOnTouchListener за RecyclerView состояния простоя и установления, но когда я попытался прикоснуться, пока это происходит, он застрял на своем текущем месте прокрутки.

Так что оба RecyclerView а также ViewPager есть препятствия для достижения цели...

Вот что у меня сейчас работает:

ViewPager:

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

RecyclerView:

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

POC код того что я пробовал (оба ViewPager а также RecyclerView):

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inflater = LayoutInflater.from(this)

        //viewPager area

        viewPager.adapter = object : RecyclerPagerAdapter<RecyclerPagerAdapter.ViewHolder>() {
            var selectedHolder: RecyclerPagerAdapter.ViewHolder? = null
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
                return object : RecyclerPagerAdapter.ViewHolder(inflater.inflate(R.layout.cell, parent, false)) {}
            }

            override fun getItemCount(): Int = 100

            override fun onBindViewHolder(holder: ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
            }

            override fun setPrimaryItem(container: ViewGroup?, position: Int, obj: Any?) {
                super.setPrimaryItem(container, position, obj)
                //TODO get the soon-to-be-selected page sooner
                val holder = obj as RecyclerPagerAdapter.ViewHolder
                if (selectedHolder != null && selectedHolder != holder)
                    (selectedHolder!!.itemView as TextView).text = position.toString()
                (holder.itemView as TextView).text = "selected:${position.toString()}"
                selectedHolder = holder
            }

        }
        viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {
                when (state) {
                    ViewPager.SCROLL_STATE_DRAGGING -> Log.d("AppLog", "onPageScrollStateChanged: SCROLL_STATE_DRAGGING")
                    ViewPager.SCROLL_STATE_IDLE -> Log.d("AppLog", "onPageScrollStateChanged: SCROLL_STATE_IDLE")
                    ViewPager.SCROLL_STATE_SETTLING -> Log.d("AppLog", "onPageScrollStateChanged: SCROLL_STATE_SETTLING")
                }
            }

            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
                Log.d("AppLog", "onPageScrolled: position:$position positionOffset :$positionOffset positionOffsetPixels:$positionOffsetPixels")
            }

            override fun onPageSelected(position: Int) {
                Log.d("AppLog", "onPageSelected:" + position)
            }
        })

        //recyclerView area

        // Not needed, as I use a library for this: LinearSnapHelper().attachToRecyclerView(recyclerView)
        recyclerView.setHasFixedSize(true)
        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun getItemCount(): Int = 100

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.cell, parent, false)) {}
            }

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
            }
        }
        recyclerView.addOnPageChangedListener { oldPosition, newPosition -> Log.d("AppLog", "OnPageChanged:$oldPosition->$newPosition") }
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            @SuppressLint("ClickableViewAccessibility")
            override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                when (newState) {
                    RecyclerView.SCROLL_STATE_IDLE -> {
                        Log.d("AppLog", "state: SCROLL_STATE_IDLE")
                        recyclerViewStateTextView.text = "state: SCROLL_STATE_IDLE"
                        //setOnTouchListener doesn't really work well. It makes the scrolling stuck
                        //                        recyclerView!!.setOnTouchListener(null)
                    }
                    RecyclerView.SCROLL_STATE_SETTLING -> {
                        //TODO when settling, block touches, and update the soon-to-be-focused page
                        Log.d("AppLog", "state: SCROLL_STATE_SETTLING")
                        recyclerViewStateTextView.text = "state: SCROLL_STATE_SETTLING"
                        //                        recyclerView!!.setOnTouchListener(object : View.OnTouchListener {
                        //                            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                        //                                return true
                        //                            }
                        //
                        //                        })
                    }
                    RecyclerView.SCROLL_STATE_DRAGGING -> {
                        Log.d("AppLog", "state: SCROLL_STATE_DRAGGING")
                        recyclerViewStateTextView.text = "state: SCROLL_STATE_DRAGGING"
                    }
                }
            }
        })
        recyclerViewStateTextView.text = "state: SCROLL_STATE_IDLE"
    }

cell.xml

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:id="@android:id/text1"
    android:textSize="36sp" tools:text="@tools:sample/lorem"/>

activity_main.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:orientation="vertical"
    tools:context="com.example.user.snapblockertest.MainActivity">

    <TextView
        android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewPager :"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="0px"
        android:layout_weight="1"/>

    <TextView
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="RecyclerView with snapping:"/>

    <com.lsjwzh.widget.recyclerviewpager.RecyclerViewPager
        android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="0px"
        android:layout_weight="1" android:orientation="horizontal"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager" app:rvp_singlePageFling="true"
        app:rvp_triggerOffset="0.1"/>

    <TextView
        android:id="@+id/recyclerViewStateTextView" android:layout_width="match_parent" android:layout_height="wrap_content"/>

</LinearLayout>

"нестандартные" зависимости файла Gradle, отсюда и здесь

//https://github.com/henrytao-me/recycler-pager-adapter
implementation "me.henrytao:recycler-pager-adapter:2.1.0"
//https://github.com/lsjwzh/RecyclerViewPager
implementation 'com.github.lsjwzh.RecyclerViewPager:lib:v1.1.2@aar'

Вопросы

  1. Как я могу получить обратный вызов, когда ViewPager/RecyclerView устанавливает, в том числе, на какой элемент будет привязан?

  2. Как я могу заблокировать сенсорные события с момента его установления до момента его бездействия? Есть ли лучший способ, чем то, что я написал (иметь кликабельный вид сверху)?


Обновить:

Кажется, что для ViewPager я мог бы использовать обратный вызов onPageSelected, чтобы узнать, на каком элементе он собирается остановиться. Интересно, что является лучшим способом получить ViewHolder его страницы таким образом. Я мог бы сохранить необходимые данные в onBindViewHolder а потом проверю сам, но мне интересно, есть ли лучший способ.

Теперь не хватает, как это сделать для RecyclerView и как заблокировать сенсорные события (если есть лучший способ, чем то, что я написал). В библиотеке есть функция под названием addOnPageChangedListener, но он вызывается после окончания заселения, поэтому здесь ничего не поделаешь.

1 ответ

Решение

Кажется, что для ViewPager я мог бы использовать обратный вызов onPageSelected, чтобы узнать, на каком элементе он собирается остановиться.

Итак, вот решение для ViewPager (используя библиотеку, которую я использовал):

    val inflater = LayoutInflater.from(this)
    val viewPagerHolders = HashMap<Int, ViewPagerViewHolder>()
    viewPager.adapter = object : RecyclerPagerAdapter<ViewPagerViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerViewHolder {
            return ViewPagerViewHolder(inflater.inflate(R.layout.cell, parent, false))
        }

        override fun getItemCount(): Int = 100

        override fun onBindViewHolder(holder: ViewPagerViewHolder, position: Int) {
            holder.textView.text = position.toString()
            viewPagerHolders.remove(holder.adapterPosition)
            holder.adapterPosition = position
            viewPagerHolders[position] = holder

        }
    }
    viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
        var selectedHolder: ViewPagerViewHolder? = null

        override fun onPageScrollStateChanged(state: Int) {}
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

        override fun onPageSelected(position: Int) {
            val holder = viewPagerHolders[position]
            if (holder != null) {
                if (selectedHolder != null && selectedHolder != holder)
                    selectedHolder!!.textView.text = selectedHolder!!.adapterPosition.toString()
                holder.textView.text = "selected:${position.toString()}"
                selectedHolder = holder
            }
        }
    })

И класс ViewHolder, который просто запоминает, какая позиция адаптера связана с ним (потому что по какой-то причине этого не хватает):

class ViewPagerViewHolder(itemView: View) : RecyclerPagerAdapter.ViewHolder(itemView) {
    val textView: TextView = itemView.findViewById(android.R.id.text1)
    var adapterPosition: Int = Int.MIN_VALUE
}

Поскольку он работает так хорошо, я решил использовать его, даже не блокируя функциональность сенсорных событий.

Еще было бы неплохо узнать, как это сделать для RecyclerView.

РЕДАКТИРОВАТЬ: для RecyclerView это можно сделать: /questions/41685778/kak-privyazat-elementyi-recyclerview-chtobyi-kazhdyij-element-x-rassmatrivalsya-kak-edinoe-tseloe-dlya-privyazki/41685780#41685780

Другие вопросы по тегам