Erkennen einer langen Presse mit Android


76

Ich benutze gerade

onTouchEvent(MotionEvent event){
}

Um zu erkennen, wann der Benutzer mein glSurfaceView drückt, gibt es eine Möglichkeit zu erkennen, wann ein langer Klick erfolgt. Ich vermute, wenn ich in den Entwicklungsdokumenten nicht viel finden kann, wird es eine Art Umgehungsmethode sein. So etwas wie ACTION_DOWN registrieren und sehen, wie lange es dauert, bis ACTION_UP.

Wie erkennt man lange Drücke auf Android mit opengl-es?

Antworten:


112

Versuche dies:

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

Kann der Gestenmelder auch Standardklicks verarbeiten?
Jack

1
Das ist die beste Antwort.
Ian

25
Hinweis: Wenn Sie bereits einen OnClickListener haben, ist es viel einfacher, einen OnLongClickListener hinzuzufügen, als einen GestureRecognizer zu verwenden (es gibt einige spezifische Probleme für GRs, die Sie vermeiden können). cf stackoverflow.com/a/4402854/153422
Adam

9
Beachten Sie, dass Sie als erstes Argument einen Kontext für die Erstellung von GestureDetector angeben sollten. Das Beispiel ist veraltet.
Antzi

1
@Modge nur zwei GestureDetectorKonstruktoren sind veraltet - Sie können den Rest der Konstruktoren verwenden.
Eido95

157

GestureDetector ist die beste Lösung.

Hier ist eine interessante Alternative. Planen Sie in onTouchEvent bei jedem ACTION_DOWN einen Runnable, der in 1 Sekunde ausgeführt werden soll. Brechen Sie bei jedem ACTION_UP oder ACTION_MOVE die geplante Ausführung ab. Wenn der Abbruch weniger als 1 Sekunde nach dem Ereignis ACTION_DOWN erfolgt, wird Runnable nicht ausgeführt.

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

17
Sie können das System-Timeout für das lange Drücken erhalten, indem Sie anrufen android.view.ViewConfiguration.getLongPressTimeout().
rekire

1
Ich habe keine Ahnung warum, aber beide Lösungen funktionieren bei mir nicht.
Eido95

2
ACTION_CANCEL sollte auch behandelt werden
Kirill Vashilo

2
Vielen Dank, @MSquare. Das ist eine großartige Idee. Beachten Sie, dass ich für das von mir verwendete Gerät den Rückruf nur als Antwort auf ACTION_UP entfernen kann. Dies liegt daran, dass es sehr bewegungsempfindlich ist und ich ACTION_MOVE-Ereignisse nicht verhindern kann. Beachten Sie auch, dass Ihr Ansatz eine natürliche Möglichkeit bietet, eine sich wiederholende Aktion zu implementieren, indem Sie Runnable rekursiv aufrufen.
stevehs17

1
@stevehs Sie müssen auf Touch Slop testen.

4

Ich habe einen Code, der ein Klicken, ein langes Klicken und eine Bewegung erkennt. Es ist ziemlich eine Kombination aus der oben gegebenen Antwort und den Änderungen, die ich durch das Einblicken in jede Dokumentationsseite vorgenommen habe.

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

Ich bestätige, dass es funktioniert, da ich es in meiner eigenen Anwendung verwendet habe.


Ich habe mich für diesen entschieden, musste jedoch "awayFlag" in "ACTION_DOWN" auf "false" setzen, bevor ich den handler.postDelayed aufrief. Andernfalls wird jeder Klick nach dem ersten langen Klick als langer Klick behandelt.
Jim Jimson

Wird initialTouchXirgendwo automatisch definiert? Ich bin verwirrt.
Divya Mamgai

3

Wenn Sie meinen, Benutzer drückt, meinen Sie einen Klick? Ein Klick ist, wenn der Benutzer nach unten drückt und dann sofort den Finger anhebt. Daher umfasst es zwei onTouch-Ereignisse. Sie sollten die Verwendung von onTouchEvent für Dinge speichern, die beim ersten Berühren oder nach der Veröffentlichung auftreten.

Daher sollten Sie onClickListener verwenden, wenn es sich um einen Klick handelt.

Ihre Antwort ist analog: Verwenden Sie onLongClickListener.


1
Kann ich onClickListener nur mit einer GLSurfaceView verwenden? Ich hatte den Eindruck, dass Sie onClickListener nur mit UI-Widgets wie Schaltflächen verwenden können. Könnte aber falsch sein.
Jack

Ah, ich sehe es cool, Mann, aber gibt es eine Möglichkeit, die Koordinaten des Klicks durch onClickListener
Jack

onClickListener scheint für GLSurfaceView nicht zu funktionieren, obwohl ich es in ein FrameLayout eingewickelt habe. Ich habe versucht, den Listener auf die Oberflächenansicht selbst und das Rahmenlayout einzustellen, aber es hat nicht funktioniert = /
Jack

Diese Frage scheint spezifisch für GLSurfaceView zu sein. Für andere Ansichten ist OnLongClickListener jedoch im Allgemeinen der richtige Weg.
Sarang

3

Ich habe ein Snippet erstellt, das von der eigentlichen View-Quelle inspiriert ist und lange Klicks / Druckvorgänge mit einer benutzerdefinierten Verzögerung zuverlässig erkennt. Aber es ist in 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

Die Lösung von MSquare funktioniert nur, wenn Sie ein bestimmtes Pixel halten. Dies ist jedoch eine unangemessene Erwartung für einen Endbenutzer, es sei denn, er verwendet eine Maus (was er nicht tut, er verwendet Finger).

Also habe ich einen kleinen Schwellenwert für den Abstand zwischen der DOWN- und der UP-Aktion hinzugefügt, falls sich dazwischen eine MOVE-Aktion befindet.

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

Ein einfacherer Ansatz, der für mich funktioniert hat, besteht darin, ACTION_MOVE einfach zu ignorieren.
stevehs17

Ah, ich erinnerte mich nicht einmal daran, dass ich auf diese Weise verfolge, "wenn ich irgendwo auf den Bildschirm
tippe

1

Die Idee ist es, eine Runnable in Zukunft langen Klick für die Ausführung zu Diese Ausführung kann jedoch aufgrund eines Klicks oder einer Verschiebung abgebrochen werden.

Sie müssen auch wissen, wann ein langer Klick verbraucht wurde und wann er abgebrochen wird, weil sich der Finger zu stark bewegt hat. Wir verwenden initialTouchX&initialTouchY um zu überprüfen, ob der Benutzer einen quadratischen Bereich von 10 Pixel, 5 auf jeder Seite, verlässt.

Hier ist mein vollständiger Code zum Delegieren von Click & LongClick von Cellin ListViewnach Activitymit 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;
    }

ClickDelegateist ein interfacezum Senden von Klickereignissen an die Handlerklasse wie einActivity

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

Und alles, was Sie brauchen, ist, es in Ihrem Activityoder einem Elternteil zu implementieren, Viewwenn Sie das Verhalten delegieren müssen:

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

0
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

Hier ist ein Ansatz, der auf der guten Idee von MSquare zum Erkennen eines langen Tastendrucks basiert und eine zusätzliche Funktion aufweist: Es wird nicht nur ein Vorgang als Reaktion auf einen langen Druck ausgeführt, sondern der Vorgang wird wiederholt, bis eine MotionEvent.ACTION_UP-Nachricht angezeigt wird empfangen. In diesem Fall sind die Aktionen für langes und kurzes Drücken gleich, können jedoch unterschiedlich sein.

Beachten Sie, dass, wie andere berichtet haben, das Entfernen des Rückrufs als Antwort auf eine MotionEvent.ACTION_MOVE-Nachricht verhinderte, dass der Rückruf jemals ausgeführt wurde, da ich meinen Finger nicht ruhig genug halten konnte. Ich habe dieses Problem umgangen, indem ich diese Nachricht ignoriert habe.

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

Dies ist klug, aber es entfernt auch die Statusanimation, dh Welligkeit beim Klicken usw.
John Sardinha

0

Option: Benutzerdefinierter Detektor class

abstract public class
Long_hold
extends View.OnTouchListener
{
  public@Override boolean
  onTouch(View view, MotionEvent touch)
  {
    switch(touch.getAction())
    {
    case ACTION_DOWN: down(touch); return true;
    case ACTION_MOVE: move(touch);
    }
    return true;
  }

  private long
  time_0;
  private float
  x_0, y_0;

  private void
  down(MotionEvent touch)
  {
    time_0= touch.getEventTime();
    x_0= touch.getX();
    y_0= touch.getY();
  }

  private void
  move(MotionEvent touch)
  {
    if(held_too_short(touch) {return;}
    if(moved_too_much(touch)) {return;}

    long_press(touch);
  }
  abstract protected void
  long_hold(MotionEvent touch);
}

verwenden

private double
moved_too_much(MotionEvent touch)
{
  return Math.hypot(
      x_0 -touch.getX(),
      y_0 -touch.getY()) >TOLERANCE;
}

private double
held_too_short(MotionEvent touch)
{
  return touch.getEventTime()-time_0 <DOWN_PERIOD;
}

wo

  • TOLERANCE ist die maximal tolerierte Bewegung

  • DOWN_PERIOD ist die Zeit, die man drücken muss

import

static android.view.MotionEvent.ACTION_MOVE;
static android.view.MotionEvent.ACTION_DOWN;

in Code

setOnTouchListener(new Long_hold()
  {
  protected@Override boolean
  long_hold(MotionEvent touch)
  {
    /*your code on long hold*/
  }
});

-2

Ich habe eine Lösung gefunden und es ist nicht erforderlich, lauffähige oder andere Dinge zu definieren, und es funktioniert gut.

    var lastTouchTime: Long = 0

    // ( ViewConfiguration.#.DEFAULT_LONG_PRESS_TIMEOUT =500)
    val longPressTime = 500

    var lastTouchX = 0f
    var lastTouchY = 0f

    view.setOnTouchListener { v, event ->

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                lastTouchTime = SystemClock.elapsedRealtime()
                lastTouchX = event.x
                lastTouchY = event.y
                return@setOnTouchListener true
            }
            MotionEvent.ACTION_UP -> {
                if (SystemClock.elapsedRealtime() - lastTouchTime > longPressTime
                        && Math.abs(event.x - lastTouchX) < 3
                        && Math.abs(event.y - lastTouchY) < 3) {
                    Log.d(TAG, "Long press")
                }
                return@setOnTouchListener true
            }
            else -> {
                return@setOnTouchListener false
            }
        }

    }

Erkennt keine lange Druckgeste, wird Ihnen nur sagen, dass es eine lange Presse nach der Tat war.
Daedsidog

Es funktioniert perfekt zum Erkennen von langem Drücken. Bitte überprüfen Sie es noch einmal.
Kinjal Patel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.