Обнаружение долгого нажатия с Android
Я сейчас пользуюсь
onTouchEvent(MotionEvent event){
}
чтобы определить, когда пользователь нажимает мой glSurfaceView, есть способ определить, когда сделан длинный щелчок. Я предполагаю, что если я не смогу найти что-нибудь в документации для разработчиков, то это будет своего рода обходной метод. Что-то вроде регистрации ACTION_DOWN и просмотра времени до ACTION_UP.
Как вы обнаруживаете длительные нажатия на Android с помощью OpenGL-ES?
8 ответов
Попробуй это:
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);
};
GestureDetector - лучшее решение.
Вот интересная альтернатива. В onTouchEvent для каждого ACTION_DOWN запланируйте запуск Runnable в течение 1 секунды. На каждом ACTION_UP или ACTION_MOVE отмените запланированный Runnable. Если отмена происходит менее чем через 1 с после события ACTION_DOWN, Runnable не запустится.
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);
}
У меня есть код, который обнаруживает щелчок, длинный щелчок и движение. Это довольно сочетание ответа, приведенного выше, и изменений, которые я сделал, заглянув на каждую страницу документации.
//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;
}
Я подтверждаю, что это работает, поскольку я использовал это в своем собственном приложении.
Я создал фрагмент, вдохновленный реальным источником View, который надежно распознает длинные нажатия и нажатия с пользовательской задержкой. Но это в Котлине
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
}
Когда вы имеете в виду нажатия пользователя, вы имеете в виду щелчок? Щелчок - это когда пользователь нажимает, а затем сразу же поднимает палец вверх. Поэтому он охватывает два события onTouch. Вы должны сохранить использование onTouch Event для вещей, которые происходят при первом касании или после релиза.
Таким образом, вы должны использовать onClickListener, если это клик.
Ваш ответ аналогичен: используйте onLongClickListener.
Решение от MSquare работает только в том случае, если вы держите определенный пиксель, но это неоправданное ожидание для конечного пользователя, если только он не использует мышь (а они не используют пальцы).
Поэтому я добавил немного порогового значения для расстояния между действием ВНИЗ и ВВЕРХ, если между ними было действие ПЕРЕМЕЩЕНИЕ.
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);
}
Идея создания Runnable
для выполнения длинного щелчка в будущем, но это выполнение может быть отменено из-за щелчка или перемещения.
Вам также необходимо знать, когда использовался длинный щелчок и когда он отменен, потому что палец слишком много двигался. Мы используем initialTouchX
& initialTouchY
для проверки, если пользователь выходит из квадратной области 10 пикселей, 5 с каждой стороны.
Вот мой полный код для делегирования Click & LongClick от Cell
в ListView
в Activity
с 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
является interface
для отправки событий щелчка в класс обработчика, как Activity
public interface ClickDelegate {
boolean onItemClick(int position);
boolean onItemLongClick(int position);
}
И все, что вам нужно, это реализовать это в своем Activity
или родитель View
если вам нужно делегировать поведение:
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
}
}
}
Опция : пользовательский детектор 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);
}
использовать
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;
}
где
TOLERANCE
это максимально допустимое движениеDOWN_PERIOD
время нужно нажимать
import
static android.view.MotionEvent.ACTION_MOVE;
static android.view.MotionEvent.ACTION_DOWN;
в коде
setOnTouchListener(new Long_hold()
{
protected@Override boolean
long_hold(MotionEvent touch)
{
/*your code on long hold*/
}
});
Вот подход, основанный на прекрасной идее MSquare для обнаружения длительного нажатия кнопки, который имеет дополнительную функцию: операция не только выполняется в ответ на долгое нажатие, но операция повторяется до тех пор, пока сообщение MotionEvent.ACTION_UP не будет получено. В этом случае действия длинного и короткого нажатия одинаковы, но они могут быть разными.
Обратите внимание, что, как сообщали другие, удаление обратного вызова в ответ на сообщение MotionEvent.ACTION_MOVE предотвратило выполнение обратного вызова, так как я не мог держать палец достаточно. Я обошел эту проблему, проигнорировав это сообщение.
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");
}
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;
}
});
Я нашел одно решение, и оно не требует определения runnable или других вещей, и оно работает нормально.
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
}
}
}