【问题标题】:MotionLayout prevents ClickListener on all ViewsMotionLayout 阻止所有视图上的 ClickListener
【发布时间】:2020-08-04 07:08:38
【问题描述】:

我正在使用带有场景 xml 的 MotionLayout:

<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>

这 2 个 ConstraintSets 仅引用 2 个视图 ID:v_notifications_containerv_top_sheet

在我的 Activity 中,我想将一个普通的 ClickListener 设置为此 MotionLayout 中的其他视图之一:

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

这行被执行了,但是 ClickListener 从未被触发。我搜索了其他帖子,但其中大多数都涉及在 motion:touchAnchorId 的同一视图上设置 ClickListener。这不是这里的情况。 ClickListener 设置为在 MotionLayout 设置中未曾提及的 View。如果我删除 app:layoutDescription 属性,点击就可以了。

我也尝试使用setOnTouchListener,但它也从未被调用过。

如何在 MotionLayout 中设置点击监听器?

【问题讨论】:

  • &lt;Constraint android:id="@id/iv_notification_status"&gt; &lt;PropertySet app:applyMotionScene="false" app:visibilityMode="ignore" /&gt; &lt;/Constraint&gt;你能试试dis吗?
  • 不错的方法,但不幸的是没有区别。

标签: android onclicklistener android-constraintlayout android-motionlayout


【解决方案1】:

要在视图上设置 onClick 操作,请使用:

android:onClick="handleAction"

在 MotionLayout 文件中,并在您的类中定义“handleAction”。

【讨论】:

  • android:onClick 属性除了按照我的方式设置 ClickListener 之外什么也没做。尽管如此:我尝试了它并得到了相同的结果。我也试过直接在VMandroid:onClick="@{(view) -&gt; myViewModel.test()}"中绑定一个方法。也没有反应
【解决方案2】:

this great medium article 的帮助下,我发现 MotionLayout 正在拦截点击事件,即使运动场景仅包含 OnSwipe 过渡。

所以我编写了一个自定义的 MotionLayout 来仅处理 ACTION_MOVE 并将所有其他触摸事件向下传递到视图树。像魅力一样工作:

/**
 * 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
    }

}

【讨论】:

  • 你能详细说明一下如何使用它吗?谢谢!
  • 而且,如果我在一行上有一个滑动过渡并单击该行中的一个按钮怎么办?我猜这种方法会破坏点击转换,对吧?
  • 有时还是会出现奇怪的行为
  • @TimonNetherlands 回答有用吗?然后我会将他的答案标记为正确。
【解决方案3】:

@muetzenflo 的回复是迄今为止我看到的解决此问题的最有效的解决方案。

但是,仅检查 Event.Action 中的 MotionEvent.ACTION_MOVE 会导致 MotionLayout 响应不佳。如下例所示,最好使用ViewConfiguration.TapTimeout 来区分移动和单击。

public class MotionSubLayout extends MotionLayout {

    private long mStartTime = 0;

    public MotionSubLayout(@NonNull Context context) {
        super(context);
    }

    public MotionSubLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MotionSubLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
            mStartTime = event.getEventTime();
        } else if ( event.getAction() == MotionEvent.ACTION_UP ) {
            if ( event.getEventTime() - mStartTime <= ViewConfiguration.getTapTimeout() ) {
                return false;
            }
        }

        return super.onInterceptTouchEvent(event);
    }
}

【讨论】:

  • MotionEvent.ACTION_UP 实际上从未在像素 4 手机上使用此实现触发
【解决方案4】:

@TimonNetherlands 代码的小修改也适用于 pixel4

class ClickableMotionLayout: MotionLayout {
    private var mStartTime: Long = 0
    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_DOWN ) {
            mStartTime = event.eventTime;
        }

        if ((event?.eventTime?.minus(mStartTime)!! >= ViewConfiguration.getTapTimeout()) && event.action == MotionEvent.ACTION_MOVE) {
            return super.onInterceptTouchEvent(event)
        }

        return false;
    }
}

【讨论】:

    【解决方案5】:

    @Finn Marquardt 的解决方案是有效的,但在我看来,仅对 ViewConfiguration.getTapTimeout() 进行检查并不是 100% 可靠,对我来说,有时点击事件不会触发,因为点击的持续时间大于 @987654322 @(只有 100 毫秒)。也不处理长按。

    这是我的解决方案,使用GestureDetector:

    class ClickableMotionLayout : MotionLayout {
     
    private var isLongPressing = false
    private var compatGestureDetector : GestureDetectorCompat? = null
    var gestureListener : GestureDetector.SimpleOnGestureListener? = null
    
    init {
          setupGestureListener()
          setOnTouchListener { v, event ->
              if(isLongPressing && event.action == MotionEvent.ACTION_UP){
                  isPressed = false
                  isLongPressing = false
                  v.performClick()
              } else {
                  isPressed = false
                  isLongPressing = false
                  compatGestureDetector?.onTouchEvent(event) ?: false
              }
          }
      }
    

    setupGestureListener() 的实现方式如下:

     fun setupGestureListener(){
            gestureListener = object : GestureDetector.SimpleOnGestureListener(){
                override fun onLongPress(e: MotionEvent?) {
                    isPressed = progress == 0f
                    isLongPressing = progress == 0f
                }
    
                override fun onSingleTapUp(e: MotionEvent?): Boolean {
                    isPressed = true
                    performClick()
                    return true
                }
            }
            compatGestureDetector = GestureDetectorCompat(context, gestureListener)
        }
    

    GestureDetector 仅在点击或长按时处理触摸事件(并且它将手动触发“按下”状态)。一旦用户抬起手指并且触摸事件实际上是长按,就会触发点击事件。在任何其他情况下,MotionLayout 将处理该事件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-07
      • 2015-06-21
      • 1970-01-01
      • 2020-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多