How to quickly get the selected page when setting up the binding on RecyclerView and ViewPager

Background

ViewPager binds to the view after scrolling, and therefore can RecyclerView if you use something like this:

 LinearSnapHelper().attachToRecyclerView(recyclerView) 

Or using the library to snap to a specific edge, as shown in this library . It can imitate ViewPager almost completely if you want, as shown in this library mentioned by CommonsWare here .

Both of them can function in the same way, if necessary (binding to one view, not necessarily with full free space), therefore this question concerns both of them.

There are some processing problems in ViewPager, but they can be fixed, for example, using this library .

Problem

I need to update the interface of the new view shown as soon as the RecyclerView / ViewPager is idle in the scroll.

Of course, since the user can still touch it, it would be a problem to know what should happen, so I should also block touch events when it sets the scroll state.

This means, for example, that when a user throws one page from page 0 to page 1, as soon as the link starts, tap the blocked events and I can find out that he is linking to page 1 and refresh this page.

The problem is that both RecyclerView and ViewPager do not offer this feature. I can get an element that was selected only after it stopped scrolling, and not while it settles.

What i tried

For ViewPager adapter has setPrimaryItem , so unfortunately it tells me which item is selected after it has completed the setup. I have an addOnPageChangeListener function that tells me about the scroll position at any given time (using onPageScrolled ), but it doesnโ€™t tell me which direction I am going (left / right), and I canโ€™t know what data will be displayed and which will be displayed . addOnPageChangeListener also provides scroll status (downtime, subsidence, drag and drop).

For RecyclerView I can get the scroll state callback when it is about to install and when it is idle, but I donโ€™t see how to get the element it is about to install about.

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

I tried using setOnTouchListener for the RecyclerView idle and settling states, but when I tried to touch it when it was installed, it got stuck in the current scroll location.

So both of RecyclerView and ViewPager have obstacles to doing all this ...

Here is what I got so far:

ViewPager:

enter image description here

RecyclerView:

enter image description here

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 files are "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' 

Questions

  • How can I get a callback when the ViewPager / RecyclerView sets, including which element will be bound to?

  • How to block touch events from the moment of its establishment to the moment of its idle time? Is there a better way than what I wrote (with clickable top view)?


Update:

It seems that for the ViewPager, I could use the onPageSelected callback to get the element that it is going to set. Interestingly, the best way to get the ViewHolder on my page this way. I could save the necessary data in onBindViewHolder and then check it myself, but I am wondering if there is a better way.

Now there is not enough how to do this for the 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 is called after the completion of the settlement, so this will not help here.

+1
android android-viewpager snap-framework android-recyclerview
source share
1 answer

It looks like for ViewPager, I could use the onPageSelected callback to get the element it is about to set about.

So, here is the solution for ViewPager (using the library 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 the ViewHolder class, which simply remembers which adapter position is associated with it (because for some reason this is missing):

 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 to use it without even blocking the functionality of touch events.

It would still be nice to know how to do this for RecyclerView.

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

0
source share

All Articles