Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution: Horizontal Scrolling Inside a Card #354

Open
StuStirling opened this issue Oct 19, 2021 · 0 comments
Open

Solution: Horizontal Scrolling Inside a Card #354

StuStirling opened this issue Oct 19, 2021 · 0 comments

Comments

@StuStirling
Copy link

Like many others, I needed to have a RecyclerView (ViewPager2) within my cards. Here is how I worked around it.

Disable/Enable Horizontal Swiping

I needed to be able to conditionally disable and enable horizontal swiping of the cards based on events within an item. To do so, I had a listener that could be passed into the adapter upon creation.

interface MatchItemListener {
    fun disableCardSwipe()
    fun enableCardSwipe()
}

And here is the integration of this listener upon adapter creation. Notice we are disabling/enabling the horizontal scrolling in the CardStackLayoutManager assigned to our parent's layoutManager.

listener = object : MatchItemListener {
        override fun disableCardSwipe() {
            cardStackLayoutManager.setCanScrollHorizontal(false)
        }

        override fun enableCardSwipe() {
            cardStackLayoutManager.setCanScrollHorizontal(true)
        }
    }
adapter = MyAdapter(listener)

Intercepting Touches

Now we need to intercept the touches in the child items to disable/enable horizontal swiping in the parent's CardStackLayoutManager.

First, create a custom ViewGroup. In this instance, mine is a ConstraintLayout. In this we are doing some collision detection to see if the touch occurred in the view where we want to scroll horizontally. If its within this view, we call the listener to disable horizontal card swiping, otherwise we enable it again.

class MatchItemContainer @kotlin.jvm.JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    var listener: MatchItemListener? = null

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        val x = round(ev.x).toInt()
        val y = round(ev.y).toInt()

        if (isTouchInsideView(findViewById<ViewPager2>(R.id.carousel),x, y)) {
            listener?.disableCardSwipe()
        } else listener?.enableCardSwipe()

        return super.dispatchTouchEvent(ev)
    }

    private fun isTouchInsideView(view: View, x: Int, y: Int): Boolean =
        (x > view.left && x < view.right) &&
            (y > view.top && y < view.bottom)
}

Setting Listener on Each Child

We can share the same listener on each of the children. When the adapter creates the ViewHolder or whatever mechanism you're using to inflate your layout into a view, we assign the listener to our custom ViewGroup.

Here is how I'm doing it (I'm using the FastAdapter library so its inflating a ViewBinding but the principle is the same).

override fun createBinding(inflater: LayoutInflater, parent: ViewGroup?): ItemMatchItemBinding =
        ItemMatchItemBinding.inflate(inflater, parent, false).apply {
            container.listener = listener
        }

Hope this helps someone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant