Warm tip: This article is reproduced from serverfault.com, please click

MotionLayout prevents ClickListener on all Views

发布于 2020-04-21 13:49:58

I am using a MotionLayout with a scene-xml:

<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    >
    <OnSwipe
        motion:touchAnchorId="@+id/v_top_sheet"
        motion:touchRegionId="@+id/v_top_sheet_touch_region"
        motion:touchAnchorSide="bottom"
        motion:dragDirection="dragDown" />
</Transition>

The 2 ConstraintSets are referencing only 2 View IDs: v_notifications_container and v_top_sheet.

In my Activity I want to set a normal ClickListener to one of the other Views in this MotionLayout:

iv_notification_status.setOnClickListener { Timber.d("Hello") }

This line is executed, but the ClickListener is never triggered. I searched other posts, but most of them deal with setting a ClickListener on the same View that is the motion:touchAnchorId. This is not the case here. The ClickListener is set to a View that is not once mentioned in the MotionLayout setup. If I remove the app:layoutDescription attribute, the click works.

I also tried to use setOnTouchListener, but it is also never called.

How can I set a click listener within a MotionLayout?

Questioner
muetzenflo
Viewed
0
muetzenflo 2020-05-10 19:35:52

With the help of this great medium article I figured out that MotionLayout is intercepting click events even though the motion scene only contains an OnSwipe transition.

So I wrote a customized MotionLayout to only handle ACTION_MOVE and pass all other touch events down the View tree. Works like a charm:

/**
 * MotionLayout will intercept all touch events and take control over them.
 * That means that View on top of MotionLayout (i.e. children of MotionLayout) will not
 * receive touch events.
 *
 * If the motion scene uses only a onSwipe transition, all click events are intercepted nevertheless.
 * This is why we override onInterceptTouchEvent in this class and only let swipe actions be handled
 * by MotionLayout. All other actions are passed down the View tree so that possible ClickListener can
 * receive the touch/click events.
 */
class ClickableMotionLayout: MotionLayout {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        if (event?.action == MotionEvent.ACTION_MOVE) {
            return super.onInterceptTouchEvent(event)
        }
        return false
    }

}