Виявлення тривалого натискання за допомогою Android


77

В даний час я використовую

onTouchEvent(MotionEvent event){
}

щоб виявити, коли користувач натискає мій glSurfaceView, чи є спосіб визначити, коли робиться довгий клацання. Я здогадуюсь, якщо я не зможу знайти багато чого у розробницьких документах, тоді це буде якийсь метод обходу. Щось на зразок реєстрації ACTION_DOWN і перегляду того, скільки часу до ACTION_UP.

Як виявити тривалі натискання на андроїд за допомогою opengl-es?

Відповіді:


113

Спробуйте це:

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

Чи може детектор жестів також обробляти стандартні клацання?
Джек,

25
Примітка: якщо у вас вже є OnClickListener, набагато простіше додати OnLongClickListener, ніж використовувати GestureRecognizer (є деякі проблеми, характерні для GR, яких можна уникнути). cf stackoverflow.com/a/4402854/153422
Адам,

9
Зверніть увагу, що ви повинні надати контекст створення GestureDetector як перший аргумент. Приклад застарілий.
Antzi

Щоб уникнути підкласу подання, використовуйте View # setOnTouchListener () . Див. "Зйомка подій дотику для одного перегляду" в " Виявлення загальних жестів" .
Панг

1
@Modge не підтримує лише GestureDetectorконструктори - ви можете використовувати решту конструкторів.
Eido95,

157

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

17
Ви можете отримати час очікування системи для тривалого натискання, зателефонувавши android.view.ViewConfiguration.getLongPressTimeout().
rekire

2
Я не уявляю, чому, але обидва рішення для мене не працюють.
Eido95,

2
Також слід обробляти
ACTION_CANCEL

2
Дякую, @MSquare. Це чудова ідея. Зверніть увагу, що для пристрою, яким я користуюся, я можу видалити зворотний дзвінок лише у відповідь на ACTION_UP; це тому, що він дуже чутливий до руху, і я не можу запобігти подіям ACTION_MOVE. Зауважте також, що ваш підхід забезпечує природний спосіб реалізувати повторювану дію, викликаючи Runnable рекурсивно.
stevehs17

1
@stevehs Вам потрібно протестувати на сенсорний шар.

4

У мене є код, який виявляє клацання, довгий клацання та рух. Це досить поєднання відповіді, наведеної вище, та змін, які я зробив, заглянувши на кожну сторінку документації.

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

Я підтверджую, що він працює, оскільки я використовував його у власному додатку.


Я пішов із цим, але повинен був встановити falseFlag як false у ACTION_DOWN, перш ніж викликати handler.postDelayed, інакше кожен клік після першого тривалого клацання трактується як довгий клацання.
Джим

Чи має initialTouchXпевні отримати автоматично де - небудь? Я збентежений.
Divya Mamgai

3

Маючи на увазі натискання користувача, ви маєте на увазі клацання? Клацання - це коли користувач натискає вниз, а потім негайно піднімає палець вгору. Тому він охоплює дві події onTouch. Вам слід заощадити використання onTouchEvent для речей, які трапляються при першому дотику або після випуску.

Таким чином, ви повинні використовувати onClickListener, якщо це клік.

Ваша відповідь аналогічна: використовуйте onLongClickListener.


1
Чи можу я використовувати onClickListener лише з GLSurfaceView? У мене склалося враження, що користувачем onClickListener можна користуватися лише за допомогою віджетів інтерфейсу, таких як кнопки. Однак може бути помилковим.
Джек,

Ах, я бачу це круто, але чи є спосіб отримати координати кліку через onClickListener
Джек

onClickListener, здається, не працює для GLSurfaceView, навіть якщо я його загорнув у FrameLayout. Я спробував встановити слухач на сам вигляд поверхні та макет кадру, але це не спрацювало = /
Джек

Це питання, схоже, є специфічним для GLSurfaceView, однак загалом для інших подань OnLongClickListener - це шлях.
Саранг,

3

Я створив фрагмент, натхненний фактичним джерелом перегляду, який надійно виявляє довгі клацання / натискання із власною затримкою. Але це в Котліні:

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

Рішення 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);
}

Більш простий підхід, який працював для мене, це просто ігнорування ACTION_MOVE.
stevehs17

Ах, я навіть не пам'ятаю , що це те , як я відстеження «якщо довго натисніть в будь-якому місці на екрані , а потім»
EpicPandaForce

1

Ідея полягає у створенні Runnableдля виконання тривалого клацання в майбутньому, але це виконання можна скасувати через клацання або переміщення.

Вам також потрібно знати, коли було витрачено тривалий клацання, а коли воно скасовано, оскільки палець рухався занадто сильно. Ми використовуємо initialTouchX& initialTouchYдля перевірки, чи виходить користувач із квадрата площею 10 пікселів, по 5 з кожної сторони.

Ось мій повний код для делегування Click & LongClick з Cellin ListViewto 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
        }
    }
}

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

Ось підхід, заснований на приємній ідеї 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");   
}

Це розумно, але воно також усуває анімацію стану, тобто пульсацію при натисканні тощо
Джон

0

варіант: нестандартний детектор 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*/
  }
});

-2

Я знайшов одне рішення, і воно не вимагає визначення запущених чи інших речей, і воно працює нормально.

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

    }

Не виявляє довгого натискання, лише скаже вам, що це було довге натискання після факту.
daedsidog

Він чудово працює для виявлення тривалого натискання. Будь ласка, ще раз перевірте.
kinjal patel
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.