如何在解决 RecyclerView 和 ViewPager 上的捕捉功能时获得即将被选中的页面 [英] How to get soon-to-be-selected page while settling snapping feature on RecyclerView and ViewPager

查看:24
本文介绍了如何在解决 RecyclerView 和 ViewPager 上的捕捉功能时获得即将被选中的页面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

ViewPager 在你执行一些滚动后会捕捉到一个视图,如果你使用类似这样的东西,RecyclerView 也可以:

LinearSnapHelper().attachToRecyclerView(recyclerView)

或者通过使用库来捕捉到某个边缘,如

回收站视图:

我尝试过的 POC 代码(ViewPagerRecyclerView):

MainActivity.kt

类 MainActivity : AppCompatActivity() {覆盖 fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val inflater = LayoutInflater.from(this)//viewPager 区域viewPager.adapter = 对象:RecyclerPagerAdapter() {var selectedHolder:RecyclerPagerAdapter.ViewHolder?=空覆盖乐趣 onCreateViewHolder(父:ViewGroup,viewType:Int):ViewHolder {返回对象:RecyclerPagerAdapter.ViewHolder(inflater.inflate(R.layout.cell, parent, false)) {}}覆盖有趣的 getItemCount(): Int = 100覆盖乐趣 onBindViewHolder(holder: ViewHolder, position: Int) {(holder.itemView as TextView).text = position.toString()}覆盖有趣的 setPrimaryItem(container: ViewGroup?, position: Int, obj: Any?) {super.setPrimaryItem(容器,位置,obj)//TODO 尽快获取即将被选中的页面val holder = obj 作为 RecyclerPagerAdapter.ViewHolderif (selectedHolder != null && selectedHolder != 持有人)(selectedHolder!!.itemView as TextView).text = position.toString()(holder.itemView as TextView).text = "selected:${position.toString()}"selectedHolder = 持有者}}viewPager.addOnPageChangeListener(对象:ViewPager.OnPageChangeListener {覆盖乐趣 onPageScrollStateChanged(state: Int) {当(状态){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")}}覆盖乐趣 onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {Log.d("AppLog", "onPageScrolled: position:$position positionOffset :$positionOffset positionOffsetPixels:$positionOffsetPixels")}覆盖乐趣 onPageSelected(position: Int) {Log.d("AppLog", "onPageSelected:" + 位置)}})//recyclerView区域//不需要,因为我为此使用了一个库:LinearSnapHelper().attachToRecyclerView(recyclerView)recyclerView.setHasFixedSize(true)recyclerView.adapter = 对象:RecyclerView.Adapter() {覆盖有趣的 getItemCount(): Int = 100覆盖乐趣 onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {返回对象:RecyclerView.ViewHolder(inflater.inflate(R.layout.cell, parent, false)) {}}覆盖乐趣 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(对象:RecyclerView.OnScrollListener(){@SuppressLint("ClickableViewAccessibility")覆盖乐趣 onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {super.onScrollStateChanged(recyclerView, newState)当(新状态){RecyclerView.SCROLL_STATE_IDLE ->{Log.d("AppLog", "状态:SCROLL_STATE_IDLE")recyclerViewStateTextView.text = "状态:SCROLL_STATE_IDLE"//setOnTouchListener 并不能很好地工作.它使滚动卡住//recyclerView!!.setOnTouchListener(null)}RecyclerView.SCROLL_STATE_SETTLING ->{//结算时的TODO,阻止touch,更新即将被关注的页面Log.d("AppLog", "状态:SCROLL_STATE_SETTLING")recyclerViewStateTextView.text = "状态:SCROLL_STATE_SETTLING"//recyclerView!!.setOnTouchListener(object : View.OnTouchListener {//覆盖 fun onTouch(v: View?, event: MotionEvent?): Boolean {//返回真//}////})}RecyclerView.SCROLL_STATE_DRAGGING ->{Log.d("AppLog", "状态:SCROLL_STATE_DRAGGING")recyclerViewStateTextView.text = "状态:SCROLL_STATE_DRAGGING"}}}})recyclerViewStateTextView.text = "状态:SCROLL_STATE_IDLE"}

cell.xml

activity_main.xml

gradle 文件非标准"依赖项,来自这里和这里

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

问题

  1. 如何获取 ViewPager/RecyclerView 何时稳定的回调,包括即将捕捉到的项目?

  2. 如何阻止触摸事件从它稳定到空闲的时间?有没有比我写的更好的方法(顶部有一个可点击的视图)?

<小时>

更新:

似乎对于 ViewPager,我可以使用 onPageSelected 回调以获取它即将解决的项目.想知道以这种方式获取其页面的 ViewHolder 的最佳方法是什么.我可以将需要的数据保存在 onBindViewHolder 中,然后自己查看,不知道有没有更好的方法.

现在缺少的是如何为 RecyclerView 执行此操作以及如何阻止触摸事件(如果有比我写的更好的方法).该库有一个名为 addOnPageChangedListener 的函数,但它是在设置完成后调用的,因此在这里无济于事.

解决方案

似乎对于 ViewPager,我可以使用 onPageSelected 回调来获取它即将解决的项目.

所以这是 ViewPager 的解决方案(使用我使用的库):

 val inflater = LayoutInflater.from(this)val viewPagerHolders = HashMap<Int, ViewPagerViewHolder>()viewPager.adapter = 对象:RecyclerPagerAdapter() {覆盖乐趣 onCreateViewHolder(父:ViewGroup,viewType:Int):ViewPagerViewHolder {返回 ViewPagerViewHolder(inflater.inflate(R.layout.cell, parent, false))}覆盖有趣的 getItemCount(): Int = 100覆盖乐趣 onBindViewHolder(holder: ViewPagerViewHolder, position: Int) {holder.textView.text = position.toString()viewPagerHolders.remove(holder.adapterPosition)holder.adapterPosition = 位置viewPagerHolders[位置] = 持有者}}viewPager.addOnPageChangeListener(对象:ViewPager.OnPageChangeListener {var selectedHolder: ViewPagerViewHolder?=空覆盖乐趣 onPageScrollStateChanged(state: Int) {}覆盖乐趣 onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}覆盖乐趣 onPageSelected(position: Int) {val 持有人 = viewPagerHolders[位置]如果(持有人!= null){if (selectedHolder != null && selectedHolder != 持有人)selectedHolder!!.textView.text = selectedHolder!!.adapterPosition.toString()holder.textView.text = "选择:${position.toString()}"selectedHolder = 持有者}}})

还有一个 ViewHolder 类,它只记住与之关联的适配器位置(因为由于某种原因缺少它):

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

由于它运行良好,我决定使用它,即使没有阻止触摸事件功能.

仍然很高兴知道如何为 RecyclerView 做这件事.

对于 RecyclerView,可以这样做:https://stackoverflow.com/a/47580753/878126

Background

ViewPager snaps to a view after you perform some scrolling, and so can RecyclerView, if you use something like this:

LinearSnapHelper().attachToRecyclerView(recyclerView)

Or by using a library to snap to a certain edge, as shown on this library. It can mimic ViewPager almost entirely if you wish, as shown on this library, mentioned by CommonsWare here.

Both of them can function the same way if needed (snapping to a single view, optionally taking whole available space), so this question is about both of them.

ViewPager has some issues of recycling views, but those can be fixed, for example using this library.

The problem

I'm required to update the UI of the newly shown view, as soon as the RecyclerView/ViewPager is about to be idle in scrolling.

Of course, since the user can still touch it, it would be a problem of knowing what is about to occur, so I'm required to also block touch events when it's settling the scroll state.

This means, for example, that when the user flings a single page, from page 0 to page 1, as soon as the snapping starts, touch events are blocked, and I would be able to know that it snaps to page 1 and update this page.

The problem here is that both RecyclerView and ViewPager don't offer this functionality. I can get the item that's selected only after it stopped scrolling, not while it is settling.

What I've tried

For ViewPager, the adapter only has setPrimaryItem , so sadly it tells me which item is selected after it finished settling. I do have addOnPageChangeListener function, which tells me about the scrolling position at any given time (using onPageScrolled), but it doesn't tell me which direction I go (left/right), and I can't know which data and which viewHolder of it is about to be selected. addOnPageChangeListener also provides the state of the scrolling (idle, settling, dragging).

For RecyclerView I can get a callback of the scrolling state, of when it's about to settle and when it becomes idle, but I can't see how to get the item it's about to settle on.

About blocking touch events, I think I could put a clickable view (that has no content, so it's invisible to the user) on top of it when it's settling, and hide it (set visibility to GONE) when it's idle, but I wonder if there is a better way.

I tried using setOnTouchListener for RecyclerView idle and settling states, but when I tried touching while it's settling, it got stuck on its current scrolling location.

So both of RecyclerView and ViewPager have obstacles for getting it all done...

Here's what I got currently working :

ViewPager:

RecyclerView:

POC code of what I tried (both ViewPager and 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 file "non-standard" dependencies, from here and here

//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'

The questions

  1. How can I get a callback of when the ViewPager/RecyclerView is settling, including which item is about to be snapped to ?

  2. How can I block the touch events from the time it's settling, till the time it's idle? Is there a better way than what I wrote (of having a clickable view on top) ?


Update:

Seems that for ViewPager, I could use the onPageSelected callback to get which item it's about to settle on. Wonder though what's the best way to get the ViewHolder of its page this way. I could save the needed data in onBindViewHolder and then check it myself, but I wonder if there is a better way.

Now what's missing is how to do it for RecyclerView and how to block touch events (if there is a better way than what I wrote). The library has a function called addOnPageChangedListener , but it's called after the settling has finished, so it can't help here.

解决方案

Seems that for ViewPager, I could use the onPageSelected callback to get which item it's about to settle on.

So here's the solution for ViewPager (using the library I used) :

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

And a ViewHolder class that just remembers which adapter position is associated with it (because this is missing for some reason) :

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

Since it works so well, I decided I will use it, even without blocking touch events functionality.

Still could be nice to know how to do it for RecyclerView.

EDIT: for RecyclerView, this can be done: https://stackoverflow.com/a/47580753/878126

这篇关于如何在解决 RecyclerView 和 ViewPager 上的捕捉功能时获得即将被选中的页面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆