Reputation: 165
I would like to simultaneously scroll horizontally a set of ViewPagers
using a swiping gesture. The user should be able to slowly scroll to the desired position at each point of the gesture (i.e. the ViewPager
s scrolling should align with the x-axis of the user gesture. I almost achieved what I needed, however in my implementation, both ViewPager
s scrolling are not fully 'locked/synchronised', if the user makes a very small gesture one of the ViewPager
quickly and magnetically scrolls to the next page while the other one stay at the finger position. I don't know if this is due to the magnetic effect of the pages on a ViewPager
but it seems so. I wonder if anyone has an idea how to solve this issue.
I uploaded a demo project here: https://github.com/JoseGbel/sync-scrolling-multiple-viewpager2
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewPager1 = findViewById<ViewPager2>(R.id.view_pager1)
val viewPager2 = findViewById<ViewPager2>(R.id.view_pager2)
val layout = findViewById<ConstraintLayout>(R.id.layout)
val stringsForVP1 = listOf(
"At vero eos et accusamus et iusto odio dignissimos",
"ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti"
)
val stringsForVP2 = listOf(
"ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti",
"At vero eos et accusamus et iusto odio dignissimos"
)
viewPager1.adapter = ViewPagerAdapter(this, stringsForVP1)
viewPager2.adapter = ViewPagerAdapter(this, stringsForVP2)
layout.setOnTouchListener(object : OnSwipeTouchListener([email protected] as Context) {
override fun onSwipe(offset: Float?) {
offset?.let {
val offset = convertPixelsToDp(it, applicationContext)
viewPager1.beginFakeDrag()
viewPager1.fakeDragBy(offset)
viewPager1.endFakeDrag()
viewPager2.beginFakeDrag()
viewPager2.fakeDragBy(offset)
viewPager2.endFakeDrag()
}
super.onSwipe(offset)
}
})
}
}
class ViewPagerAdapter(
private val context: Context,
private val items: List<String>
) : RecyclerView.Adapter<ViewPagerAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
fun bind(string: String) {
val textView = itemView.findViewById<TextView>(R.id.string_fragment_value_text_view)
textView.text = string
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(R.layout.view_pager_element, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}
fun convertPixelsToDp(px: Float, context: Context): Float {
return px / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
}
Custom OnSwipeListener
open class OnSwipeTouchListener(ctx: Context) : View.OnTouchListener {
private val gestureDetector: GestureDetector
var difference : Float? = null
var startingXCoordinate : Float? = null
init {
gestureDetector = GestureDetector(ctx, GestureListener())
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
if(event.action == MotionEvent.ACTION_DOWN){
startingXCoordinate = event.rawX
}
if(event.action == MotionEvent.ACTION_MOVE) {
difference = event.rawX - (startingXCoordinate ?: throw IllegalStateException("Starting coordinate was not defined"))
onSwipe(difference)
}
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
return true
}
}
open fun onSwipe(offset: Float?) {}
}
Upvotes: 0
Views: 763
Reputation: 165
The code below did the trick for me. Although I had to switch to ViewPager1
viewPager1.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
viewPager2.scrollX = scrollX
}
viewPager2.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
viewPager1.scrollX = scrollX
}
Upvotes: 1