androidx / constraintlayout

ConstraintLayout is an Android layout component which allows you to position and size widgets in a flexible way
Apache License 2.0
1.07k stars 177 forks source link

How to add click listener to Carousel item #621

Open litao0621 opened 2 years ago

litao0621 commented 2 years ago

I'm having trouble adding an onClickListener to my carousel's items. I've tried setting the onClickListener in the adapter like so:

carousel.setAdapter(object : Carousel.Adapter {
  override fun populate(view: View?, index: Int) {
    view.setOnClickListener {
      // do something
    }
  }
})

However, this makes the carousel unresponsive to swiping. When I try to swipe the carousel it executes my onClick function but doesn't execute my onSwipe transition.

Would really appreciate any help!

litao0621 commented 2 years ago

I made several changes based on MotionLayout.It worked for me right now

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.constraintlayout.motion.widget.MotionLayout
import kotlin.math.abs

/**
 * @author : litao
 * @email  : onresume@live.com
 * @date   : 2022/5/31 5:31 
 */
class TestMotionLayout constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
) : MotionLayout(context, attrs, defStyleAttr) {

    private var mInitX = 0f
    private var mInitY = 0f

    private var mTouchSlop = 10

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {

        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                mInitX = ev.x
                mInitY = ev.y
            }
            MotionEvent.ACTION_MOVE -> {
                val moveX = abs(ev.x - mInitX)
                val moveY = abs(ev.y - mInitY)

                if (moveX > mTouchSlop || moveY > mTouchSlop){

                    val obtain = MotionEvent.obtain(ev)
                    obtain.action = MotionEvent.ACTION_DOWN
                    dispatchTouchEvent(obtain)
                    onTouchEvent(obtain)
                    return true
                }
            }
            MotionEvent.ACTION_UP -> {
            }
        }
        return false
    }

}

Could you please supply a better solution to this problem

vassilisimon commented 2 years ago

Use this snippet if you place your carousel as a row item in a recyclerview.

override fun onInterceptTouchEvent(event: MotionEvent?): Boolean = true

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                previousX = event.x.toInt()
                previousY = event.y.toInt()
                hasActionMoveOccurred = false
            }
            MotionEvent.ACTION_UP -> {
                disableTouchEventsForParent(false)

                if (hasActionMoveOccurred.not()) {
                    onClick(carousel.currentIndex)
                    return true
                }
            }
            MotionEvent.ACTION_MOVE -> {
                val xDifferenceAbsolute = abs(calculateDistanceX(event))
                val yDifferenceAbsolute = abs(calculateDistanceY(event))
                hasActionMoveOccurred = xDifferenceAbsolute > scaledTouchSlop || yDifferenceAbsolute > scaledTouchSlop

                if (isHorizontallyScrolling) {
                    return super.onTouchEvent(event)
                }

                val scrolledHorizontally = xDifferenceAbsolute > yDifferenceAbsolute
                disableTouchEventsForParent(scrolledHorizontally)
            }
        }
        return super.onTouchEvent(event)
    }

    private fun disableTouchEventsForParent(disable: Boolean) {
        isHorizontallyScrolling = disable
        parent.requestDisallowInterceptTouchEvent(disable)
    }

    private fun calculateDistanceX(currentMotionEvent: MotionEvent): Int = previousX - currentMotionEvent.x.toInt()
    private fun calculateDistanceY(currentMotionEvent: MotionEvent): Int = previousY - currentMotionEvent.y.toInt()

You could do it like that, but if a user clicks while the "swipe motion" is in progress, it will trigger the onClick call on the old currentIndex of the carousel. So basically you can't differentiate which item was clicked.

Would be nice, if there was a ItemClickListener for the carousel. @jafu888

jafu888 commented 2 years ago

I am trying to figure out what sort of attribute we should add to enable the behavior you want. I do not want to modify the default behavior.

For Carousel index: I do not want to make assumptions on what is in the Carousel.

The simplest way to know what you clicked on is to setTag("MY_INDEX" , index) on the view in when populate it.

vassilisimon commented 2 years ago

The easiest solution for now was to ignore clicks while the motionlayout is in transition.

prasanthaviator commented 6 months ago

Any updates on this issue. Please recommend any solution.