Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

SOLVED: How to sync scrolling first-positions of 2 RecyclerViews?

android developer:

Background

I have 2 Recyclerview instances. One is horizontal, and the second is vertical.

They both show the same data and have the same amount of items, but in different ways, and the cells are not necessary equal in size through each of them .

I wish that scrolling in one will sync with the other, so that the first item that's shown on one, will always be shown on the other (as the first).

The problem

Even though I've succeeded making them sync (I just choose which one is the "master", to control the scrolling of the other), the direction of the scrolling seems to affect the way it works.

Suppose for a moment the cells have equal heeight:

If I scroll up/left, it works as I intended, more or less:

However, if I scroll down/right, it does let the other RecyclerView show the first item of the other, but usually not as the first item:

Note: on the above screenshots, I've scrolled in the bottom RecyclerView, but a similar result will be with the top one.

The situation gets much worse if, as I wrote, the cells have different sizes:

What I've tried

I tried to use other ways of scrolling and going to other positions, but all attempts fail.

Using smoothScrollToPosition made things even worse (though it does seem nicer), because if I fling, at some point the other RecyclerView takes control of the one I've interacted with.

I think I should use the direction of the scrolling, together with the currently shown items on the other RecyclerView.

Here's the current (sample) code. Note that in the real code, the cells might not have equal sizes (some are tall, some are short, etc...). One of the lines in the code makes the cells have different height.

activity_main.xml


xmlns:android="http://ift.tt/nIICcg" xmlns:app="http://ift.tt/GEGVYd"
xmlns:tools="http://ift.tt/LrGmb4" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">

android:id="@+id/topReccyclerView" android:layout_width="0dp" android:layout_height="100dp"
android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/horizontal_cell"/>

android:id="@+id/bottomRecyclerView" android:layout_width="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/topReccyclerView"
tools:listitem="@layout/horizontal_cell"/>


horizontal_cell.xml


android:id="@+id/textView" xmlns:android="http://ift.tt/nIICcg"
xmlns:tools="http://ift.tt/LrGmb4" android:layout_width="100dp" android:layout_height="100dp"
android:gravity="center" tools:text="@tools:sample/lorem"/>

vertical_cell.xml


android:id="@+id/textView" xmlns:android="http://ift.tt/nIICcg"
xmlns:tools="http://ift.tt/LrGmb4" android:layout_width="match_parent" android:layout_height="50dp"
android:gravity="center" tools:text="@tools:sample/lorem"/>

MainActivity


class MainActivity : AppCompatActivity() {
var masterView: View? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = LayoutInflater.from(this)
topReccyclerView.adapter = object : RecyclerView.Adapter() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
}

override fun getItemCount(): Int {
return 100
}

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

bottomRecyclerView.adapter = object : RecyclerView.Adapter() {
val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
// this makes the heights of the cells different from one another:
holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
}

override fun getItemCount(): Int {
return 100
}

override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
}
}
LinearSnapHelper().attachToRecyclerView(topReccyclerView)
LinearSnapHelper().attachToRecyclerView(bottomRecyclerView)
topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
}

inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
var lastItemPos: Int = Int.MIN_VALUE
val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)

override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.d("AppLog", "onScrollStateChanged:$thisRecyclerViewId $newState")
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
Log.d("AppLog", "setting $thisRecyclerViewId to be master")
masterView = thisRecyclerView
}
RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
Log.d("AppLog", "resetting $thisRecyclerViewId from being master")
masterView = null
lastItemPos = Int.MIN_VALUE
}
}
}

override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView))
return
// Log.d("AppLog", "onScrolled:$thisRecyclerView $dx-$dy")
val currentItem = (thisRecyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (lastItemPos == currentItem)
return
lastItemPos = currentItem
otherRecyclerView.scrollToPosition(currentItem)
// otherRecyclerView.smoothScrollToPosition(currentItem)
Log.d("AppLog", "currentItem:" + currentItem)
}
}
}

The questions

  1. How do I let the other RecycerView to always have the first item the same as the currently controlled one?

  2. How to modify the code to support smooth scrolling, without causing the issue of suddenly having the other RecyclerView being the one that controls ?



Posted in S.E.F
via StackOverflow & StackExchange Atomic Web Robots
This Question have been answered
HERE


This post first appeared on Stack Solved, please read the originial post: here

Share the post

SOLVED: How to sync scrolling first-positions of 2 RecyclerViews?

×

Subscribe to Stack Solved

Get updates delivered right to your inbox!

Thank you for your subscription

×