2
votes

MotionLayout empêche ClickListener sur toutes les vues

J'utilise un MotionLayout avec un scene-xml:

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

Les 2 ConstraintSets font référence qu'à 2 ID de vue: v_notifications_container et v_top_sheet .

Dans mon activité, je souhaite définir un ClickListener normal sur l'une des autres vues de ce 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>

Cette ligne est exécutée, mais le ClickListener n'est jamais déclenché. J'ai recherché d'autres articles, mais la plupart d'entre eux traitent de la définition d'un ClickListener sur la même vue que le motion:touchAnchorId . Ce n'est pas le cas ici. ClickListener est défini sur une vue qui n'est pas mentionnée une seule fois dans la configuration de MotionLayout. Si je supprime l'attribut app:layoutDescription , le clic fonctionne.

J'ai également essayé d'utiliser setOnTouchListener , mais il n'est jamais appelé non plus.

Comment définir un écouteur de clics dans un MotionLayout?


2 commentaires

<Constraint android:id="@id/iv_notification_status"> <PropertySet app:applyMotionScene="false" app:visibilityMode="ignore" /> </Constraint> pouvez-vous essayer dis s'il vous plaît?


Belle approche, mais ne fait malheureusement aucune différence.


3 Réponses :


0
votes

Pour configurer l'action onClick sur la vue, utilisez:

android:onClick="handleAction"

dans le fichier MotionLayout, et définissez "handleAction" dans votre classe.


1 commentaires

L'attribut android:onClick ne fait rien d'autre que de définir un ClickListener comme je l'ai fait. Néanmoins: je l'ai essayé et j'ai obtenu le même résultat. J'ai également essayé de lier directement une méthode dans la VM android:onClick="@{(view) -> myViewModel.test()}" . Pas de réaction non plus



3
votes

Avec l'aide de cet excellent article médiatique, j'ai compris que MotionLayout intercepte les événements de clic même si la scène de mouvement ne contient qu'une transition OnSwipe.

J'ai donc écrit un MotionLayout personnalisé pour gérer uniquement ACTION_MOVE et transmettre tous les autres événements tactiles dans l'arborescence de la vue. Fonctionne comme un charme:

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

}


3 commentaires

Pouvez-vous expliquer un peu comment l'utiliser? Merci!


Et que se passe-t-il si j'ai une transition de balayage sur une ligne et un clic sur un bouton de cette ligne? Cette approche briserait la transition de clic, je suppose, non?


Il apparaît encore parfois un comportement étrange



1
votes

La réponse de @ muetzenflo est la solution la plus efficace que j'ai vue jusqu'à présent pour ce problème.

Cependant, la seule vérification de Event.Action pour MotionEvent.ACTION_MOVE provoque une MotionLayout réponse de MotionLayout . Il est préférable de faire la différence entre le mouvement et un simple clic en utilisant ViewConfiguration.TapTimeout comme le montre l'exemple ci-dessous.

public class MotionSubLayout extends MotionLayout {

    private Long mStartTime;

    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);
    }
}


1 commentaires

cool, merci beaucoup pour le code mis à jour!