onTouchEvent ()에서 이동과 클릭을 구별하는 방법은 무엇입니까?


78

내 응용 프로그램에서 이동 및 클릭 이벤트를 모두 처리해야합니다.

클릭은 하나의 ACTION_DOWN 작업, 여러 ACTION_MOVE 작업 및 하나의 ACTION_UP 작업의 시퀀스입니다. 이론적으로 ACTION_DOWN 이벤트가 발생한 다음 ACTION_UP 이벤트가 발생하면 사용자가 방금보기를 클릭했음을 의미합니다.

그러나 실제로이 시퀀스는 일부 장치에서 작동하지 않습니다. 내 Samsung Galaxy Gio에서보기 : ACTION_DOWN, ACTION_MOVE, ACTION_UP을 여러 번 클릭하면 이러한 시퀀스가 ​​나타납니다. 즉, ACTION_MOVE 액션 코드로 예상치 못한 OnTouchEvent가 발생합니다. ACTION_DOWN-> ACTION_UP 시퀀스를 전혀 (또는 거의) 얻지 않습니다.

또한 클릭 위치를 제공하지 않기 때문에 OnClickListener를 사용할 수 없습니다. 그렇다면 클릭 이벤트를 감지하고 이동과 어떻게 다릅니 까?


onTouchEvent 대신 onTouch를 사용해 보셨나요? 이렇게하면 뷰에 대한 참조도 갖게되므로 값을 로그 아웃하고 ACTION_DOWN 및 ACTION_UP 인 클릭이 호출되었는지 여부를 확인할 수 있습니다.
Arif Nadeem

답변:


116

여기에 매우 간단하고 손가락이 움직이는 것에 대해 걱정할 필요가없는 또 다른 솔루션이 있습니다. 단순히 이동 한 거리만큼 클릭을 기반으로하는 경우 클릭과 긴 클릭을 어떻게 구별 할 수 있습니까?

여기에 더 많은 스마트를 넣고 이동 한 거리를 포함 할 수 있지만, 사용자가 200 밀리 초 안에 이동할 수있는 거리가 클릭이 아닌 이동을 구성해야하는 경우를 아직 만나지 못했습니다.

setOnTouchListener(new OnTouchListener() {
    private static final int MAX_CLICK_DURATION = 200;
    private long startClickTime;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                startClickTime = Calendar.getInstance().getTimeInMillis();
                break;
            }
            case MotionEvent.ACTION_UP: {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                if(clickDuration < MAX_CLICK_DURATION) {
                    //click event has occurred
                }
            }
        }
        return true;
    }
});

ViewConfiguration.getLongPressTimeout()클릭의 최대 시간으로 사용하지 않는 이유는 무엇 입니까?
Flo

1
길게 누르면 그 방법을 확실히 사용할 수 있지만 이것은 표준 클릭을위한 것입니다.
Stimsoni

9
실제로 event.getEventTime () -event.getDownTime ()을 사용하여 클릭 지속 시간을 계산하는 것이 더 나을 수 있습니다
Grimmy

@Grimmy 왜 그게 더 좋을까요?
RestInPeace

4
@RestInPeace는 별도의 필드가 필요하지 않고 일정으로 작업 할 필요가 없기 때문입니다. 하나의 숫자를 다른 숫자에서 빼면됩니다.
Grimmy

55

다음을 고려하여 최상의 결과를 얻었습니다.

  1. 주로 와 이벤트 간에 이동 한 거리 입니다. 다른 화면을 더 잘 지원하기 위해 최대 허용 거리를 픽셀이 아닌 밀도 독립형 픽셀 로 지정하고 싶었습니다 . 예 : 15 DP .ACTION_DOWNACTION_UP
  2. 둘째, 이벤트 사이 의 기간 입니다. 1 초 는 최대로 보였습니다. (어떤 사람들은 아주 "엄청나게", 즉 천천히 "클릭"합니다. 저는 여전히 그것을 인식하고 싶습니다.)

예:

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            break;
        }
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, e.getX(), e.getY()) < MAX_CLICK_DISTANCE) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}

여기의 아이디어는 Gem의 솔루션 과 동일하지만 다음과 같은 차이점이 있습니다.

  • 이것은 두 점 사이 의 실제 유클리드 거리를 계산합니다 .
  • 이것은 px 대신 dp를 사용합니다.

업데이트 (2015) : Gabriel의 미세 조정 된이 .


훌륭한 대답! 이것은 상당한 연구를 절약했습니다. 감사합니다!
2Dee

이 답변에 대한 한 가지 질문, 이동 이벤트 내에서 거리를 기록하는 것이 더 나을 것이라고 생각하십니까? 뷰에서 손가락으로 시작하면 화면 상단으로 스 와이프했다가 1 초 이내에 하단으로 다시 내려갈 수 있지만 여전히 클릭으로 계산됩니다. 이동 동작 내에 기록 된 경우 이벤트가 클릭 영역을 초과 한 경우 부울을 설정할 수 있습니다.
Gabriel

@Gabriel : 오랜만에이게 제 필요에 "충분히"충분하다고 생각합니다. 그 방법으로 더 나은 결과를 얻을 경우 실험 자유롭게, 그렇다면 :-) 새로운 해답을 게시 고려
Jonik

대부분의 사람들보다 좀 더 구체적인 사용 사례가 있다고 생각합니다. 화면 하단에서 상단으로 드래그 할 수있는 뷰가 있고, 헤더를 클릭하여 위치를 전환 할 수 있습니다. 이것은 당신이 그것을 위로 끌었다가 다시 빨리 내려도 여전히 클릭을 인식하고 있음을 의미합니다. 감사합니다. 귀하의 솔루션은 제가 필요로하는 것의 거의 100 %였습니다. 제가 거기에 도달하기 위해 약간의 수정을가했습니다. 좀 더 정확한 정보가 필요한 사람들을위한 대체 답변으로 오늘 밤에 내 코드를 게시 할 것입니다.
가브리엘

2
아, 그리고 당신 onTouchEvent()은 반환 값이 없습니다. (당신이 거기에 쓸 것을 우리에게 보여 주면 도움이 될 것이다 return true;? return false;? return super.onTouchEvent(e);?)

29

촬영 Jonik의 우위를 내가 손가락을 이동 한 후 놓아 전에 자리로 돌아갈 경우 클릭으로 등록하지 않는 약간 더 미세 조정 버전을 내장 :

그래서 여기 내 해결책이 있습니다.

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;
private boolean stayedWithinClickDistance;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            stayedWithinClickDistance = true;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) {
                stayedWithinClickDistance = false;
            }
            break;
        }     
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}

21

감지기를 사용하면 작동하며 드래그시 상승하지 않습니다.

들:

private GestureDetector mTapDetector;

초기화 :

mTapDetector = new GestureDetector(context,new GestureTap());

이너 클래스 :

class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onDoubleTap(MotionEvent e) {

        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // TODO: handle tap here
        return true;
    }
}

onTouch :

@Override
public boolean onTouch(View v, MotionEvent event) {
    mTapDetector.onTouchEvent(event);
    return true;
}

즐겨 :)


좋아하는 솔루션. 그러나 여러보기에 대해 하나의 터치 / 제스처 리스너를 사용하려면 마지막 onTouch 이벤트 동안 마지막보기를 추적해야하므로 onSingleTapConfirmed 이벤트의 출처를 알 수 있습니다. MotionEvent가 뷰를 저장하면 좋을 것입니다.
AlanKley

이 코드를 실행하는 데 문제가있을 수있는 사람을 위해 onTouch (View v, MotionEvent event) 함수의 반환 값을 "true"로 설정하여이 작업을 수행해야합니다. 사실 우리가 이벤트를 소비하고 사실을 반환해야한다고 생각합니다.
黄锐铭

6

클릭 이벤트 인식을 최적화하려면 다음 두 가지를 고려해야합니다.

  1. ACTION_DOWN과 ACTION_UP 사이의 시차입니다.
  2. 사용자가 터치 할 때와 손가락을 뗄 때의 x, y의 차이.

사실 저는 Stimsoni와 Neethirajan의 논리를 결합합니다.

그래서 여기 내 해결책이 있습니다.

        view.setOnTouchListener(new OnTouchListener() {

        private final int MAX_CLICK_DURATION = 400;
        private final int MAX_CLICK_DISTANCE = 5;
        private long startClickTime;
        private float x1;
        private float y1;
        private float x2;
        private float y2;
        private float dx;
        private float dy;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            // TODO Auto-generated method stub

                    switch (event.getAction()) 
                    {
                        case MotionEvent.ACTION_DOWN: 
                        {
                            startClickTime = Calendar.getInstance().getTimeInMillis();
                            x1 = event.getX();
                            y1 = event.getY();
                            break;
                        }
                        case MotionEvent.ACTION_UP: 
                        {
                            long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                            x2 = event.getX();
                            y2 = event.getY();
                            dx = x2-x1;
                            dy = y2-y1;

                            if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) 
                                Log.v("","On Item Clicked:: ");

                        }
                    }

            return  false;
        }
    });

+1, 좋은 접근 방식. 나는 px 대신 dp를 사용 하고 거리를 조금 다르게 계산하는 것을 제외하고 는 비슷한 솔루션을 사용했습니다 MAX_CLICK_DISTANCE.
Jonik 2011

위에서 선언 된 이러한 변수는 전역 적으로 선언되어야합니다.
Rushi Ayyappa

@RushiAyyappa 대부분의 경우보기에서 onTouchListener를 한 번 설정합니다. 은 setOnTouchListener()익명 클래스 참조를합니다. 변수가 클래스 내에서 전역 인 경우. 누군가 setOnTouchListener()다른 청취자 인스턴스로 다시 전화하지 않으면 작동한다고 생각합니다 .
Gem

내 touchListener는 내부 변수이고이 변수는 두 번째로 파괴 되었기 때문에 전역 적으로 선언해야했고 이것이 작동했습니다.
Rushi Ayyappa

6

길 SH 응답을 사용하여, 나는 구현하여 개선 onSingleTapUp()보다는 onSingleTapConfirmed(). 훨씬 더 빠르며 드래그 / 이동하면보기를 클릭하지 않습니다.

GestureTap :

public class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        button.performClick();
        return true;
    }
}

다음과 같이 사용하십시오.

final GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureTap());
button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        gestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_UP:
                return true;
            case MotionEvent.ACTION_MOVE:
                return true;
        }
        return false;
    }
});

이 답변은 TextView의 스크롤과 터치를 구별하는 데 도움이되었습니다. 이 답변의 더욱 몸매는 여전 하구나 버전을 찾을 수 있습니다 여기 (코 틀린)
Matteljay

4

아래 코드는 문제를 해결할 것입니다

    @우세하다
        public boolean onTouchEvent (MotionEvent event) {
            switch (event.getAction ()) {
                case (MotionEvent.ACTION_DOWN) :
                    x1 = event.getX ();
                    y1 = event.getY ();
                    단절;
                case (MotionEvent.ACTION_UP) : {
                    x2 = event.getX ();
                    y2 = event.getY ();
                    dx = x2-x1;
                    dy = y2-y1;

                if (Math.abs (dx)> Math.abs (dy)) 
                {
                    if (dx> 0) move (1); //권리
                    그렇지 않으면 (dx == 0) move (5); //딸깍 하는 소리
                    그렇지 않으면 move (2); //왼쪽
                } 
                그밖에 
                {
                    if (dy> 0) move (3); // 아래로
                    그렇지 않으면 (dy == 0) move (5); //딸깍 하는 소리
                    그렇지 않으면 move (4); //쪽으로
                }
                }
            }
            true를 반환하십시오.
        }


3

ACTION_MOVE가 발생하지 않고 ACTION_DOWN이 발생하는 것은 매우 어렵습니다. 첫 번째 터치가 발생한 위치와 다른 지점에서 화면에서 손가락을 조금만 움직이면 MOVE 이벤트가 트리거됩니다. 또한 손가락 압력의 변화도 MOVE 이벤트를 유발할 것이라고 생각합니다. Action_Move 메서드에서 if 문을 사용하여 원래 DOWN 동작에서 이동이 발생한 거리를 확인하려고합니다. 이동이 일정 반경 밖에서 발생하면 MOVE 동작이 발생합니다. 아마도 최선의 자원 효율적인 방법은 아니지만 작동해야합니다.


감사합니다. 저도 이와 같은 계획을 세웠지 만 누군가가 다른 해결책을 알고 있다고 생각했습니다. 이러한 동작 (클릭하는 동안 아래로-> 이동-> 위로)도 경험 했습니까?
Anastasia

예, 저는 현재 멀티 터치와 관련된 게임을 작업 중이며 poitners 및 Move_Events에 대한 광범위한 테스트를 수행했습니다. 이것은 내가 Action_Down 이벤트가 빠르게 ACTION_MOVE로 변경 가져옵니다 (테스트하는 동안 나니 결론 중 하나 다행 내가 도울 수 있습니다..
testingtester

좋아, 나는 혼자가 아니라는 것이 좋다. :) 나는 stackoverflow 또는 다른 곳에서 그러한 문제를 가진 사람을 찾지 못했습니다. 감사합니다.
Anastasia

1
어떤 장치에서 그러한 동작을 보셨습니까? 삼성 Galaxy Ace와 Gio에서 이것을 발견했습니다.
Anastasia

개인적으로 삼성 Captivate와 Galaxy S II에서 본 적이 있지만 거의 모든 장치에서 이와 같을 것이라고 생각합니다
testingtester

1

클릭만으로 반응하려면 다음을 사용하십시오.

if (event.getAction() == MotionEvent.ACTION_UP) {

}

1

위의 답변에 추가하면 onClick 및 Drag 작업을 모두 구현하려면 아래 코드를 사용할 수 있습니다. @Stimsoni의 도움을 받으십시오.

     // assumed all the variables are declared globally; 

    public boolean onTouch(View view, MotionEvent event) {

      int MAX_CLICK_DURATION = 400;
      int MAX_CLICK_DISTANCE = 5;


        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN: {
                long clickDuration1 = Calendar.getInstance().getTimeInMillis() - startClickTime;


                    startClickTime = Calendar.getInstance().getTimeInMillis();
                    x1 = event.getX();
                    y1 = event.getY();


                    break;

            }
            case MotionEvent.ACTION_UP:
            {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                    Log.d("clicked", "On Item Clicked:: ");

               //    imageClickAction((ImageView) view,rl);
                }

            }
            case MotionEvent.ACTION_MOVE:

                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    //Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                  //  Log.d("clicked", "On Item Clicked:: ");

                    //    imageClickAction((ImageView) view,rl);
                }
                else {
                    ClipData clipData = ClipData.newPlainText("", "");
                    View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);

                    //Toast.makeText(getApplicationContext(), "item dragged", Toast.LENGTH_SHORT).show();
                    view.startDrag(clipData, shadowBuilder, view, 0);
                }
                break;
        }

        return  false;
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.