web-dev-qa-db-fra.com

Détecter un appui long avec Android

J'utilise actuellement

onTouchEvent(MotionEvent event){
}

pour détecter lorsque l'utilisateur appuie sur ma glSurfaceView, existe-t-il un moyen de détecter un clic long. Je suppose que si je ne trouve pas grand chose dans les documents de développement, il s'agira d'une méthode de contournement. Quelque chose comme enregistrer ACTION_DOWN et voir combien de temps il faut avant ACTION_UP.

Comment détecter les longues pressions sur Android à l'aide d'OpenGL-ES?

62
Jack

Essaye ça:

final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
    public void onLongPress(MotionEvent e) {
        Log.e("", "Longpress detected");
    }
});

public boolean onTouchEvent(MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
};
98
Daniel Fekete

GestureDetector est la meilleure solution. 

Voici une alternative intéressante. Dans onTouchEvent sur chaque ACTION_DOWN programme un Runnable pour s'exécuter en 1 seconde. Sur chaque ACTION_UP ou ACTION_MOVE , annulez l'exécution du programme d'exécution. Si l'annulation survient moins de 1 s depuis ACTION_DOWN event, Runnable ne s'exécutera pas.

final Handler handler = new Handler(); 
Runnable mLongPressed = new Runnable() { 
    public void run() { 
        Log.i("", "Long press!");
    }   
};

@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
    if(event.getAction() == MotionEvent.ACTION_DOWN)
        handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
    if((event.getAction() == MotionEvent.ACTION_MOVE)||(event.getAction() == MotionEvent.ACTION_UP))
        handler.removeCallbacks(mLongPressed);
    return super.onTouchEvent(event, mapView);
}
142
MSquare

J'ai un code qui détecte un clic, un clic long et un mouvement ... C'est plutôt une combinaison de la réponse donnée ci-dessus et des modifications que j'ai apportées en jetant un coup d'œil dans chaque page de documentation. 

//Declare this flag globally
boolean goneFlag = false;

//Put this into the class
final Handler handler = new Handler(); 
    Runnable mLongPressed = new Runnable() { 
        public void run() { 
            goneFlag = true;
            //Code for long click
        }   
    };

//onTouch code
@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {    
        case MotionEvent.ACTION_DOWN:
            handler.postDelayed(mLongPressed, 1000);
            //This is where my code for movement is initialized to get original location.
            break;
        case MotionEvent.ACTION_UP:
            handler.removeCallbacks(mLongPressed);
            if(Math.abs(event.getRawX() - initialTouchX) <= 2 && !goneFlag) {
                //Code for single click
                return false;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handler.removeCallbacks(mLongPressed);
            //Code for movement here. This may include using a window manager to update the view
            break;
        }
        return true;
    }

Je confirme que cela fonctionne car je l'ai utilisé dans ma propre application. 

5
SanVed

Voulez-vous dire un clic lorsque vous voulez dire des utilisateurs? Un clic se produit lorsque l'utilisateur appuie sur le doigt, puis le lève immédiatement. Par conséquent, il englobe deux événements onTouch. Vous devriez enregistrer l'utilisation de onTouchEvent pour tout ce qui se passe au toucher ou après la publication.

Ainsi, vous devriez utiliser onClickListener s'il s'agit d'un clic.

Votre réponse est analogue: Utilisez onLongClickListener.

3
Ian

La solution de MSquare ne fonctionne que si vous tenez un pixel spécifique, mais ce n'est pas une attente raisonnable pour un utilisateur final, à moins qu'il utilise une souris (ce qu'il ne fait pas, il utilise des doigts).

J'ai donc ajouté un peu de seuil pour la distance entre l'action DOWN et l'action UP au cas où il y aurait une action MOVE entre les deux.

final Handler longPressHandler = new Handler();
Runnable longPressedRunnable = new Runnable() {
    public void run() {
        Log.e(TAG, "Long press detected in long press Handler!");
        isLongPressHandlerActivated = true;
    }
};

private boolean isLongPressHandlerActivated = false;

private boolean isActionMoveEventStored = false;
private float lastActionMoveEventBeforeUpX;
private float lastActionMoveEventBeforeUpY;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        longPressHandler.postDelayed(longPressedRunnable, 1000);
    }
    if(event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
        if(!isActionMoveEventStored) {
            isActionMoveEventStored = true;
            lastActionMoveEventBeforeUpX = event.getX();
            lastActionMoveEventBeforeUpY = event.getY();
        } else {
            float currentX = event.getX();
            float currentY = event.getY();
            float firstX = lastActionMoveEventBeforeUpX;
            float firstY = lastActionMoveEventBeforeUpY;
            double distance = Math.sqrt(
                    (currentY - firstY) * (currentY - firstY) + ((currentX - firstX) * (currentX - firstX)));
            if(distance > 20) {
                longPressHandler.removeCallbacks(longPressedRunnable);
            }
        }
    }
    if(event.getAction() == MotionEvent.ACTION_UP) {
        isActionMoveEventStored = false;
        longPressHandler.removeCallbacks(longPressedRunnable);
        if(isLongPressHandlerActivated) {
            Log.d(TAG, "Long Press detected; halting propagation of motion event");
            isLongPressHandlerActivated = false;
            return false;
        }
    }
    return super.dispatchTouchEvent(event);
}
1
EpicPandaForce

J'ai créé un snippet - inspiré de la source d'affichage actuelle - qui détecte de manière fiable les clics/appuis longs avec un délai personnalisé. Mais c'est à Kotlin:

val LONG_PRESS_DELAY = 500

val handler = Handler()
var boundaries: Rect? = null

var onTap = Runnable {
    handler.postDelayed(onLongPress, LONG_PRESS_DELAY - ViewConfiguration.getTapTimeout().toLong())
}

var onLongPress = Runnable {

    // Long Press
}

override fun onTouch(view: View, event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            boundaries = Rect(view.left, view.top, view.right, view.bottom)
            handler.postDelayed(onTap, ViewConfiguration.getTapTimeout().toLong())
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            handler.removeCallbacks(onLongPress)
            handler.removeCallbacks(onTap)
        }
        MotionEvent.ACTION_MOVE -> {
            if (!boundaries!!.contains(view.left + event.x.toInt(), view.top + event.y.toInt())) {
                handler.removeCallbacks(onLongPress)
                handler.removeCallbacks(onTap)
            }
        }
    }
    return true
}
1
Benjoyo

L'idée est de créer une Runnable pour exécuter un clic long dans le futur, mais cette exécution peut être annulée à cause d'un clic ou d'un déplacement.

Vous devez également savoir quand le clic long a été utilisé et quand il a été annulé car le doigt a trop bougé. Nous utilisons initialTouchX & initialTouchY pour vérifier si l'utilisateur quitte une zone carrée de 10 pixels, 5 de chaque côté.

Voici mon code complet pour déléguer Click & LongClick de Cell dans ListView à Activity avec OnTouchListener:

    ClickDelegate delegate; 
    boolean goneFlag = false;
    float initialTouchX;
    float initialTouchY;
    final Handler handler = new Handler();
    Runnable mLongPressed = new Runnable() {
        public void run() {
            Log.i("TOUCH_EVENT", "Long press!");
            if (delegate != null) {
                goneFlag = delegate.onItemLongClick(index);
            } else {
                goneFlag = true;
            }
        }
    };

    @OnTouch({R.id.layout})
    public boolean onTouch (View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
                initialTouchX = motionEvent.getRawX();
                initialTouchY = motionEvent.getRawY();
                return true;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
                if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    handler.removeCallbacks(mLongPressed);
                    return true;
                }
                return false;
            case MotionEvent.ACTION_UP:
                handler.removeCallbacks(mLongPressed);
                if (goneFlag || Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    goneFlag = false;
                    return true;
                }
                break;
        }
        Log.i("TOUCH_EVENT", "Short press!");
        if (delegate != null) {
            if (delegate.onItemClick(index)) {
                return false;
            }
        }
        return false;
    }

ClickDelegate est une interface pour envoyer des événements de clic à la classe de gestionnaire, comme un Activity

    public interface ClickDelegate {
        boolean onItemClick(int position);
        boolean onItemLongClick(int position);
    }

Et tout ce dont vous avez besoin est de l'implémenter dans votre Activity ou parent Viewsi vous devez déléguer le comportement:

public class MyActivity extends Activity implements ClickDelegate {

    //code...
    //in some place of you code like onCreate, 
    //you need to set the delegate like this:
    SomeArrayAdapter.delegate = this;
    //or:
    SomeViewHolder.delegate = this;
    //or:
    SomeCustomView.delegate = this;

    @Override
    public boolean onItemClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle click
        } else {
            return false; //if not, it could be another event
        }
    }

    @Override
    public boolean onItemLongClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle long click
        } else {
            return false; //if not, it's a click
        }
    }
}
1
IgniteCoders
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {

                int action = MotionEventCompat.getActionMasked(event);


                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        longClick = false;
                        x1 = event.getX();
                        break;

                    case MotionEvent.ACTION_MOVE:
                        if (event.getEventTime() - event.getDownTime() > 500 && Math.abs(event.getX() - x1) < MIN_DISTANCE) {
                            longClick = true;
                        }
                        break;

                    case MotionEvent.ACTION_UP:
                                if (longClick) {
                                    Toast.makeText(activity, "Long preess", Toast.LENGTH_SHORT).show();
                                } 
                }
                return true;
            }
        });
0
LesterSenk4454

Voici une approche, basée sur l’idée sympathique de MSquare pour la détection d’une longue pression sur un bouton, qui présente une caractéristique supplémentaire: non seulement une opération est exécutée en réponse à une pression longue, mais elle est répétée jusqu’à ce que le message MotionEvent.ACTION_UP soit reçu. Dans ce cas, les actions à la presse longue et à la presse courte sont les mêmes, mais elles pourraient être différentes. 

Notez que, comme d'autres l'ont signalé, la suppression du rappel en réponse à un message MotionEvent.ACTION_MOVE a empêché le rappel de s'exécuter car je ne pouvais pas garder mon doigt suffisamment longtemps. J'ai contourné ce problème en ignorant ce message. 

private void setIncrementButton() {
    final Button btn = (Button) findViewById(R.id.btn);
    final Runnable repeater = new Runnable() {
        @Override
        public void run() {
            increment();
            final int milliseconds = 100;
            btn.postDelayed(this, milliseconds);
        }
    };
    btn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent e) {
            if (e.getAction() == MotionEvent.ACTION_DOWN) {
                increment();
                v.postDelayed(repeater, ViewConfiguration.getLongPressTimeout());
            } else if (e.getAction() == MotionEvent.ACTION_UP) {
                v.removeCallbacks(repeater);
            }
            return true;
        }
    });
}

private void increment() {
    Log.v("Long Press Example", "TODO: implement increment operation");   
}
0
stevehs17