Phát hiện báo chí lâu với Android


77

Tôi hiện đang sử dụng

onTouchEvent(MotionEvent event){
}

để phát hiện khi người dùng nhấn vào glSurfaceView của tôi, có cách nào để phát hiện khi nào thực hiện một lần nhấp dài. Tôi đoán nếu tôi không thể tìm thấy nhiều thứ trong tài liệu dành cho nhà phát triển thì đó sẽ là một số loại công việc xoay quanh phương pháp. Một cái gì đó như đăng ký ACTION_DOWN và xem còn bao lâu nữa trước ACTION_UP.

Làm thế nào để bạn phát hiện các lần nhấn lâu trên Android bằng opengl-es?

Câu trả lời:


112

Thử cái này:

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

Bộ phát hiện cử chỉ có thể xử lý tốt các nhấp chuột tiêu chuẩn không?
Jack

25
Lưu ý: nếu bạn đã có OnClickListener, việc thêm OnLongClickListener sẽ dễ dàng hơn nhiều so với việc sử dụng GestureRecognizer (có một số vấn đề cụ thể đối với GR mà bạn có thể tránh). cf stackoverflow.com/a/4402854/153422
Adam

9
Lưu ý rằng bạn nên cung cấp ngữ cảnh về việc tạo GestureDetector làm đối số đầu tiên. Ví dụ này không được dùng nữa.
Antzi

Để tránh phân lớp một dạng xem, hãy sử dụng Dạng xem # setOnTouchListener () . Xem "Ghi lại các sự kiện chạm cho một chế độ xem" trong Phát hiện các cử chỉ phổ biến .
Pang

1
@Modge chỉ có các hàm GestureDetectortạo không được chấp nhận - bạn có thể sử dụng phần còn lại của các hàm tạo.
Eido95

157

GestureDetector là giải pháp tốt nhất.

Đây là một sự thay thế thú vị. Trong onTouchEvent trên mỗi ACTION_DOWN lịch một Runnable để chạy trong 1 giây. Trên mỗi ACTION_UP hoặc ACTION_MOVE , hủy kế hoạch Runnable. Nếu quá trình hủy xảy ra dưới 1 giây kể từ sự kiện ACTION_DOWN , Runnable sẽ không chạy.

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
Bạn có thể nhận được thời gian chờ hệ thống trong một thời gian dài bằng cách gọi android.view.ViewConfiguration.getLongPressTimeout().
rekire

2
Tôi không biết tại sao nhưng cả hai giải pháp đều không hiệu quả với tôi.
Eido95 19/12/16

2
ACTION_CANCEL nên cũng được xử lý
Kirill Vashilo

2
Cảm ơn bạn, @MSquare. Ý kiến ​​hay. Lưu ý rằng đối với thiết bị tôi đang sử dụng, tôi chỉ có thể xóa lệnh gọi lại khi phản hồi ACTION_UP; điều này là do nó rất nhạy cảm với chuyển động và tôi không thể ngăn các sự kiện ACTION_MOVE. Cũng lưu ý rằng cách tiếp cận của bạn cung cấp một cách tự nhiên để thực hiện một hành động lặp lại, bằng cách gọi đệ quy Runnable.
stevehs17

1
@stevehs Bạn cần kiểm tra độ dốc của cảm ứng.

4

Tôi có một mã phát hiện một nhấp chuột, một nhấp chuột dài và chuyển động. Nó khá là kết hợp giữa câu trả lời được đưa ra ở trên và những thay đổi tôi đã thực hiện khi nhìn vào mọi trang tài liệu.

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

Tôi xác nhận rằng nó đang hoạt động vì tôi đã sử dụng nó trong ứng dụng của riêng mình.


Tôi đã sử dụng cái này, nhưng phải đặt goneFlag thành false trong ACTION_DOWN trước khi gọi handler.postDelayed, nếu không mỗi lần nhấp sau lần nhấp dài đầu tiên được coi là một lần nhấp dài.
Jim Jimson

initialTouchXđược xác định tự động ở đâu đó không? Tôi bối rối.
Divya Mamgai

3

Khi bạn muốn người dùng nhấn, bạn có nghĩa là một lần nhấp? Một cú nhấp chuột là khi người dùng nhấn xuống và sau đó ngay lập tức nhấc ngón tay lên. Do đó, nó bao gồm hai Sự kiện onTouch. Bạn nên lưu việc sử dụng onTouchEvent cho những thứ xảy ra trong lần chạm đầu tiên hoặc sau khi phát hành.

Vì vậy, bạn nên sử dụng onClickListener nếu đó là một lần nhấp.

Câu trả lời của bạn là tương tự: Sử dụng onLongClickListener.


1
Tôi có thể sử dụng onClickListener chỉ với GLSurfaceView không? Tôi có ấn tượng rằng bạn chỉ có thể sử dụng onClickListener với các tiện ích giao diện người dùng như nút. Có thể sai.
Jack

Ah tôi thấy nó mát người đàn ông, nhưng là có cách nào để có được tọa độ của các nhấp chuột qua OnClickListener
Jack

onClickListener dường như không hoạt động với GLSurfaceView mặc dù tôi đã bọc nó trong FrameLayout. Tôi đã thử đặt người nghe trên chính chế độ xem bề mặt và bố cục khung hình nhưng nó không hoạt động = /
Jack

Câu hỏi này dường như dành riêng cho GLSurfaceView, tuy nhiên, nói chung, đối với các Chế độ xem khác, OnLongClickListener là cách để đi.
Sarang

3

Tôi đã tạo một đoạn mã - lấy cảm hứng từ nguồn Chế độ xem thực tế - phát hiện các lần nhấp / lần nhấn dài một cách đáng tin cậy với độ trễ tùy chỉnh. Nhưng nó ở 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

Giải pháp của MSquare chỉ hoạt động nếu bạn giữ một pixel cụ thể, nhưng đó là một kỳ vọng không hợp lý đối với người dùng cuối trừ khi họ sử dụng chuột (mà họ không sử dụng ngón tay).

Vì vậy, tôi đã thêm một chút ngưỡng cho khoảng cách giữa hành động DOWN và UP trong trường hợp có một hành động DI CHUYỂN ở giữa.

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

Một cách tiếp cận đơn giản hơn phù hợp với tôi là bỏ qua ACTION_MOVE.
stevehs17

Ah, tôi thậm chí còn không nhớ rằng đây là cách tôi đang theo dõi "nếu nhấn lâu vào bất kỳ đâu trên màn hình thì"
EpicPandaForce

1

Ý tưởng là tạo ra một Runnablenhấp chuột dài để thực thi trong tương lai, nhưng thực thi này có thể bị hủy do nhấp chuột hoặc di chuyển.

Bạn cũng cần biết, khi nào thì nhấp chuột dài được sử dụng và khi nào thì nó bị hủy do ngón tay di chuyển quá nhiều. Chúng tôi sử dụng initialTouchX& initialTouchYđể kiểm tra xem người dùng có thoát khỏi vùng hình vuông 10 pixel, mỗi cạnh 5 pixel hay không.

Đây là mã hoàn chỉnh của tôi cho ủy Click & LongClick từ Celltrong ListViewđến Activityvới 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;
    }

ClickDelegatelà một interfaceđể gửi các sự kiện nhấp chuột đến lớp trình xử lý nhưActivity

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

Và tất cả những gì bạn cần là triển khai nó trong của bạn Activityhoặc cha mẹ Viewnếu bạn cần ủy quyền hành vi:

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

Đây là một cách tiếp cận, dựa trên ý tưởng hay của MSquare để phát hiện một thao tác nhấn và giữ nút, có một tính năng bổ sung: không chỉ một thao tác được thực hiện khi nhấn lâu, mà thao tác này được lặp lại cho đến khi có thông báo MotionEvent.ACTION_UP nhận. Trong trường hợp này, các thao tác nhấn dài và nhấn ngắn là giống nhau, nhưng chúng có thể khác nhau.

Lưu ý rằng, như những người khác đã báo cáo, việc xóa lệnh gọi lại để phản hồi thông báo MotionEvent.ACTION_MOVE đã ngăn lệnh gọi lại không bao giờ được thực hiện vì tôi không thể giữ ngón tay của mình đủ. Tôi đã giải quyết vấn đề đó bằng cách bỏ qua thông báo đó.

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

Đây là thông minh, nhưng nó cũng loại bỏ các hình ảnh động của nhà nước, tức là gợn trên bấm, vv
John Sardinha

0

tùy chọn: máy dò tùy chỉnh 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);
}

sử dụng

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

Ở đâu

  • TOLERANCE là chuyển động chịu đựng tối đa

  • DOWN_PERIOD là lúc người ta phải nhấn

import

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

trong mã

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

-2

Tôi đã tìm thấy một giải pháp và nó không yêu cầu xác định có thể chạy được hoặc những thứ khác và nó hoạt động tốt.

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

    }

Không phát hiện cử chỉ nhấn lâu, sẽ chỉ cho bạn biết đó là một lần nhấn lâu sau khi thực tế.
daedsidog

Nó hoạt động hoàn hảo để phát hiện báo chí dài. Vui lòng kiểm tra lại.
kinjal patel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.