REM Code
REM Code

Reputation: 165

Scroll Multiple ViewPagers at the same time with a gesture

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 ViewPagers scrolling should align with the x-axis of the user gesture. I almost achieved what I needed, however in my implementation, both ViewPagers 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

Answers (1)

REM Code
REM Code

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

Related Questions