web-dev-qa-db-fra.com

Android: Détecter si l'utilisateur touche et traîne hors de la zone des boutons?

Sous Android, comment détecter si un utilisateur touche un bouton et se déplace hors de la région de ce bouton?

49
anticafe

Vérifiez MotionEvent.MOVE_OUTSIDE: Vérifiez MotionEvent.MOVE:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    }
    if(event.getAction() == MotionEvent.ACTION_MOVE){
        if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
            // User moved outside bounds
        }
    }
    return false;
}

REMARQUE: Si vous souhaitez cibler Android 4.0, un monde entier de nouvelles possibilités s'ouvre: http://developer.Android.com/reference/Android/view/MotionEvent.html #ACTION_HOVER_ENTER

88
Entreco

La réponse publiée par Entreco avait besoin d'un léger ajustement dans mon cas. J'ai dû remplacer:

if(!rect.contains((int)event.getX(), (int)event.getY()))

for

if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY()))

car event.getX() et event.getY() ne s'appliquent qu'à l'ImageView elle-même, pas à tout l'écran.

21
FrostRocket

J'ai ajouté une connexion dans mon OnTouch et découvert que MotionEvent.ACTION_CANCEL était touché. C'est assez bon pour moi ...

5
Boy

J'ai eu ce même problème que l'OP par lequel je voulais savoir quand (1) un View particulier a été touché ainsi que (2) lorsque la touche down a été relâchée sur le View ou (3) lorsque la touche Bas se déplaçait hors des limites de View. J'ai rassemblé les différentes réponses de ce fil pour créer une extension simple de View.OnTouchListener (Nommée SimpleTouchListener) afin que les autres n'aient pas à jouer avec l'objet MotionEvent. La source de la classe peut être trouvée ici ou au bas de cette réponse.

Pour utiliser cette classe, définissez-la simplement comme paramètre unique de la méthode View.setOnTouchListener(View.OnTouchListener) comme suit:

myView.setOnTouchListener(new SimpleTouchListener() {

    @Override
    public void onDownTouchAction() {
        // do something when the View is touched down
    }

    @Override
    public void onUpTouchAction() {
        // do something when the down touch is released on the View
    }

    @Override
    public void onCancelTouchAction() {
        // do something when the down touch is canceled
        // (e.g. because the down touch moved outside the bounds of the View
    }
});

Voici la source de la classe que vous pouvez ajouter à votre projet:

public abstract class SimpleTouchListener implements View.OnTouchListener {

    /**
     * Flag determining whether the down touch has stayed with the bounds of the view.
     */
    private boolean touchStayedWithinViewBounds;

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStayedWithinViewBounds = true;
                onDownTouchAction();
                return true;

            case MotionEvent.ACTION_UP:
                if (touchStayedWithinViewBounds) {
                    onUpTouchAction();
                }
                return true;

            case MotionEvent.ACTION_MOVE:
                if (touchStayedWithinViewBounds
                        && !isMotionEventInsideView(view, event)) {
                    onCancelTouchAction();
                    touchStayedWithinViewBounds = false;
                }
                return true;

            case MotionEvent.ACTION_CANCEL:
                onCancelTouchAction();
                return true;

            default:
                return false;
        }
    }

    /**
     * Method which is called when the {@link View} is touched down.
     */
    public abstract void onDownTouchAction();

    /**
     * Method which is called when the down touch is released on the {@link View}.
     */
    public abstract void onUpTouchAction();

    /**
     * Method which is called when the down touch is canceled,
     * e.g. because the down touch moved outside the bounds of the {@link View}.
     */
    public abstract void onCancelTouchAction();

    /**
     * Determines whether the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     *
     * @param view  the {@link View} to which the {@link MotionEvent} has been dispatched.
     * @param event the {@link MotionEvent} of interest.
     * @return true iff the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     */
    private boolean isMotionEventInsideView(View view, MotionEvent event) {
        Rect viewRect = new Rect(
                view.getLeft(),
                view.getTop(),
                view.getRight(),
                view.getBottom()
        );

        return viewRect.contains(
                view.getLeft() + (int) event.getX(),
                view.getTop() + (int) event.getY()
        );
    }
}
4
Adil Hussain

Les 2 premières réponses sont correctes, sauf lorsque la vue se trouve dans une vue de défilement: lorsque le défilement a lieu parce que vous déplacez votre doigt, il est toujours enregistré en tant qu'événement tactile mais pas en tant qu'événement MotionEvent.ACTION_MOVE. Donc, pour améliorer la réponse (qui n'est nécessaire que si votre vue est à l'intérieur d'un élément de défilement):

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if(rect != null && !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
        // User moved outside bounds
    }
    return false;
}

J'ai testé cela sur Android 4.3 et Android 4.4

Je n'ai remarqué aucune différence entre la réponse de Moritz et le top 2, mais cela s'applique également à sa réponse:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if (rect != null){
        v.getHitRect(rect);
        if(rect.contains(
                Math.round(v.getX() + event.getX()),
                Math.round(v.getY() + event.getY()))) {
            // inside
        } else {
            // outside
        }
    }
    return false;
}
2
Bart Burg

Voici une View.OnTouchListener que vous pouvez utiliser pour voir si MotionEvent.ACTION_UP a été envoyé alors que l'utilisateur avait le doigt hors de la vue:

private OnTouchListener mOnTouchListener = new View.OnTouchListener() {

    private Rect rect;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (v == null) return true;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
            return true;
        case MotionEvent.ACTION_UP:
            if (rect != null
                    && !rect.contains(v.getLeft() + (int) event.getX(),
                        v.getTop() + (int) event.getY())) {
                // The motion event was outside of the view, handle this as a non-click event

                return true;
            }
            // The view was clicked.
            // TODO: do stuff
            return true;
        default:
            return true;
        }
    }
};
1
Jared Rummler

Bien que la réponse de @FrostRocket soit correcte, vous devez également utiliser view.getX () et Y pour tenir compte des modifications des traductions:

 view.getHitRect(viewRect);
 if(viewRect.contains(
         Math.round(view.getX() + event.getX()),
         Math.round(view.getY() + event.getY()))) {
   // inside
 } else {
   // outside
 }
1
Moritz
view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (!v.isPressed()) {
            Log.e("onTouch", "Moved outside view!");
        }
        return false;
    }
});

view.isPressed Utilise view.pointInView Et inclut une touche tactile. Si vous ne voulez pas de slop, copiez simplement la logique du view.pointInView Interne (qui est public, mais caché donc il ne fait pas partie de l'API officielle et pourrait disparaître à tout moment).

view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            v.setTag(true);
        } else {
            boolean pointInView = event.getX() >= 0 && event.getY() >= 0
                    && event.getX() < (getRight() - getLeft())
                    && event.getY() < (getBottom() - getTop());
            boolean eventInView = ((boolean) v.getTag()) && pointInView;
            Log.e("onTouch", String.format("Dragging currently in view? %b", pointInView));
            Log.e("onTouch", String.format("Dragging always in view? %b", eventInView));
            v.setTag(eventInView);
        }
        return false;
    }
});
1
mpkuth