安卓:如何检测双击?

75

我在实现双击时遇到了问题。我已经实现了onGestureListenergestureDetector,但我不确定问题出在哪里。以下是我的代码:

 public class home extends TabActivity implements OnGestureListener {
    /** Called when the activity is first created. */

 private EditText queryText;
 private ResultsAdapter m_adapter;
 private ProgressDialog pd;
 final Handler h = new Handler();
 private TabHost mTabHost;
 private ArrayList<SearchItem> sResultsArr = new ArrayList<SearchItem>();
 private String queryStr;
 private JSONObject searchResponse;
 private GestureDetector gestureScanner;

 final Runnable mUpdateResults = new Runnable() {
        public void run() {
         updateListUi();
        }
    };

 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button search = (Button)findViewById(R.id.search);
        Button testButt = (Button)findViewById(R.id.testbutt);
        queryText = (EditText)findViewById(R.id.query);
        ListView lvr = (ListView)findViewById(R.id.search_results);

      //initialise the arrayAdapter
        this.m_adapter = new ResultsAdapter(home.this, R.layout.listrow, sResultsArr);
        lvr.setAdapter(this.m_adapter);
        lvr.setOnItemClickListener(new OnItemClickListener(){
   @Override
   public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
     long arg3) {
    // TODO Auto-generated method stub
         pd = ProgressDialog.show(home.this, null,"Loading products from server", true, false);

   }

        });
        gestureScanner = new GestureDetector(this,this);
        gestureScanner.setOnDoubleTapListener(new OnDoubleTapListener(){ 
            public boolean onDoubleTap(MotionEvent e) { 
                 //viewA.setText("-" + "onDoubleTap" + "-"); 
         pd = ProgressDialog.show(home.this, null,"Loading products from server", true, false);

                 return false; 
            } 
            public boolean onDoubleTapEvent(MotionEvent e) { 
                // viewA.setText("-" + "onDoubleTapEvent" + "-"); 
                 return false; 
            } 
            public boolean onSingleTapConfirmed(MotionEvent e) { 
                 //viewA.setText("-" + "onSingleTapConfirmed" + "-"); 
                 return false; 
            } 

     });


        //initialise tab contents
        mTabHost = getTabHost();
        mTabHost.addTab(mTabHost.newTabSpec("tab1").setIndicator("Home").setContent(R.id.homepage));
        mTabHost.addTab(mTabHost.newTabSpec("tab2").setIndicator("Search Results").setContent(R.id.tab2));
        mTabHost.setCurrentTab(0);

        //sets the respective listeners
        testButt.setOnClickListener(new View.OnClickListener() {
        public void onClick(View arg0) {

         if(mTabHost.getTabWidget().getVisibility()==View.GONE){
          mTabHost.getTabWidget().setVisibility(View.VISIBLE);
         }
         else{
          mTabHost.getTabWidget().setVisibility(View.GONE);
         }
        }
     });

        search.setOnClickListener(new View.OnClickListener() {
        public void onClick(View arg0) {
         sResultsArr.clear();
         queryStr = "http://rose.mosuma.com/mobile?query=" + queryText.getText().toString();
         pd = ProgressDialog.show(home.this, null,"Loading products from server", true, false);
         goSearch();
      }
     });
    }

 //updates the listUI whenever after receiving the response from the server
 public void updateListUi(){  
    if(sResultsArr.size() > 0){

       }

    try{
     String ptypename;
     int count;
     LinearLayout ptypebar = (LinearLayout)findViewById(R.id.productCat);
     ptypebar.removeAllViews();
     JSONArray ptypes = searchResponse.getJSONArray("ptypes"); 
     for(int index =0;index < ptypes.length();index++){
      JSONObject ptype = ptypes.getJSONObject(index);
      count = ptype.getInt("count");      
      ptypename = ptype.getString("ptypename"); 

      //add into tab 2's UI

      //ImageView icon = new ImageView(this);
      TextView t = new TextView(home.this);
      t.setText(ptypename + " (" + count + ")");
      ptypebar.addView(t);
     }
    }
    catch(JSONException e){

    }
   //if(m_adapter.getItems() != sResultsArr){
    ArrayList<SearchItem> a  = m_adapter.getItems(); 
    a = sResultsArr;
   //}
      m_adapter.notifyDataSetChanged();
     pd.dismiss();
 }

 public void goSearch(){
  mTabHost.setCurrentTab(1);

  //separate thread for making http request and updating the arraylist
  Thread t = new Thread() {
           public void run() {

            searchResponse = sendSearchQuery(queryStr);
            try{
             JSONArray results = searchResponse.getJSONArray("results");

             //this is stupid. i probably have to see how to make a json adapter
             for(int index =0;index < results.length();index++){

              JSONObject product = results.getJSONObject(index);

              //gets the searched products from the json object
              URL imgUrl =  new URL(product.getString("image"));
              String productname = product.getString("productname");
              String ptypename = product.getString("ptypename");
              int pid = product.getInt("pid");
              int positive = product.getInt("pos");
              int negative = product.getInt("neg");
              int neutral = product.getInt("neu");


              SearchItem item  = new SearchItem(imgUrl,productname,ptypename,neutral,positive,negative,pid);
              sResultsArr.add(item);
             }
            }
            catch(JSONException e){

            }
            catch(Exception e){

               }
            //returns back to UI therad
            h.post(mUpdateResults);
           }
       };
       t.start();
 }

 //sends a request with qry as URL
 //and receives back a JSONobject as response
 public JSONObject sendSearchQuery(String qry){
  HttpRequest r = new HttpRequest();
  JSONObject response = r.sendHttpRequest(qry);  
  return response;
 }

 @Override
 public boolean onDown(MotionEvent arg0) {
      return gestureScanner.onTouchEvent(arg0); 
 }

 @Override
 public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
   float arg3) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public void onLongPress(MotionEvent arg0) {
  // TODO Auto-generated method stub

 }

 @Override
 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
   float arg3) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public void onShowPress(MotionEvent arg0) {
  // TODO Auto-generated method stub

 }

 @Override
 public boolean onSingleTapUp(MotionEvent arg0) {
  // TODO Auto-generated method stub
  return false;
 }

还有一个问题,如果我的ListView具有onItemClickListener,Android可以检测单击或双击吗?


这个标题问题是误导性的。谁会想要摧毁一个安卓系统呢? - outis
29个回答

116

你可以使用GestureDetector。请参考以下代码:

public class MyView extends View {

    GestureDetector gestureDetector;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
                // creating new gesture detector
        gestureDetector = new GestureDetector(context, new GestureListener());
    }

    // skipping measure calculation and drawing

        // delegate the event to the gesture detector
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return gestureDetector.onTouchEvent(e);
    }


    private class GestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }
        // event when double tap occurs
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            float x = e.getX();
            float y = e.getY();

            Log.d("Double Tap", "Tapped at: (" + x + "," + y + ")");

            return true;
        }
    }
}

你可以覆盖监听器的其他方法来获取单击、fling等事件。


3
好的,在阅读您的帖子后,我想您的问题是在onDown方法中没有返回true。gesturedetector的onTouch方法被调用在您的onTouch方法中。 - Hannes Niederhausen
22
@alan 能否将此标记为答案?这篇文章确实回答了问题,并对那些专门寻找如何实现双击的未来 Google 用户非常有用。使用长按而非双击并不总是一个选择。 - Emperor Orionii
gestureDetector已经过时。 - Zar E Ahmer
3
不,它并没有被弃用,只有两个构造函数被弃用了。 - AlvaroSantisteban
要获取更多细节和更多示例,请查看这个高票答案:https://dev59.com/xITba4cB1Zd3GeqP7YYw#26529756 - BK-

78

作为GestureDetector的轻量级替代,您可以使用这个类

public abstract class DoubleClickListener implements OnClickListener {

    private static final long DOUBLE_CLICK_TIME_DELTA = 300;//milliseconds

    long lastClickTime = 0;

    @Override
    public void onClick(View v) {
        long clickTime = System.currentTimeMillis();
        if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA){
            onDoubleClick(v);
        } else {
            onSingleClick(v);
        }
        lastClickTime = clickTime;
    }

    public abstract void onSingleClick(View v);
    public abstract void onDoubleClick(View v);
}

示例:

    view.setOnClickListener(new DoubleClickListener() {

        @Override
        public void onSingleClick(View v) {

        }

        @Override
        public void onDoubleClick(View v) {

        }
    });

12
单击不起作用,您应该注册一些定时器(例如使用Handler),以便在时间间隔内没有第二次单击时执行单击。 - An Hoa
1
它可以在单击时工作。当执行双击时,您会收到对onSingleClick()的调用,然后是对onDoubleClick()的调用,这对我的用例来说很好。添加计时器将在onDoubleClick()之前删除对onSingleClick()的调用,但在事件触发之前有延迟在我的情况下是不可接受的。 - bughi
2
我明白了。从逻辑上讲,它们应该是互斥的,即要么调用onSingleClick(),要么调用onDoubleClick(),因此您应该等待以决定调用哪个,以避免后来出现错误。这是Android处理长按的方式(请参见View类中的onTouchEvent(event)方法)。另外,您的时间间隔太高了,200毫秒更合理。通常在PC上,我认为没有人会注意到双击延迟。 - An Hoa
这个对我有用。我的代码只能使用onTouchListener,我需要检测双击。然而,以此为基础并进行一些微小的修改,我让它工作了。感谢这个好主意! - rayryeng
这是我遇到的唯一可行的解决方案。我尝试了6-7种方法,但都没有奏效,但这个解决方案是十分完美的。 - userAndroid
需要注意的是,这个类不起作用,需要使用CountDownTimer或类似的修改。 - Mike76

24

为什么不使用长按呢?或者你已经在使用其他的东西了吗?长按与双击相比的优势:

  • 在UI指南中,长按是一个推荐的交互方式,而双击则不是。
  • 这是用户期望的操作; 用户可能无法找到双击操作,因为他们不会去寻找它。
  • 它已在API中处理
  • 实现双击将影响单点触摸的处理,因为您必须等待看看每个单点触摸是否变成双击才能处理它。

嗯,我正在尝试实现类似海豚浏览器的隐藏和显示选项卡的操作(由于空间限制)... 我已经为我的列表视图实现了长按功能...所以我需要一个双击。 - alan
是的,我认为这个答案相当不错。我真的遇到了触摸和双击和单击之间的冲突。这个问题困扰了我很久,最后,我认为长按是一个不错的选择。 - Howard
3
一个好的双击监听器的示例可以是一个视频视图,使其全屏查看。 - Aykut Çevik
1
顺便提一下,在安卓系统中,长按手势不再总是优于双击手势;根据常见用法,两种手势都被推荐使用:https://www.google.com/design/spec/patterns/gestures.html# - ToolmakerSteve
1
@ToolmakerSteve 我只将双击用作即时缩放操作。而长按则可以选择项目,启动拖动。这里是新链接,介绍了Material设计中的手势。 - Mikolasan
显示剩余2条评论

16

将“Bughi”“DoubleClickListner”和“Jayant Arora”计时器结合在一个封装的类中:

public abstract class DoubleClickListener implements OnClickListener {

    private Timer timer = null;  //at class level;
    private int DELAY   = 400;

    private static final long DOUBLE_CLICK_TIME_DELTA = 300;//milliseconds

    long lastClickTime = 0;

    @Override
    public void onClick(View v) {
        long clickTime = System.currentTimeMillis();
        if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA){
            processDoubleClickEvent(v);
        } else {
            processSingleClickEvent(v);
        }
        lastClickTime = clickTime;
    }



    public void processSingleClickEvent(final View v){

        final Handler handler=new Handler();
        final Runnable mRunnable=new Runnable(){
            public void run(){
                onSingleClick(v); //Do what ever u want on single click

            }
        };

        TimerTask timertask=new TimerTask(){
            @Override
            public void run(){
                handler.post(mRunnable);
            }
        };
        timer=new Timer();
        timer.schedule(timertask,DELAY);

    }

    public void processDoubleClickEvent(View v){
        if(timer!=null)
        {
            timer.cancel(); //Cancels Running Tasks or Waiting Tasks.
            timer.purge();  //Frees Memory by erasing cancelled Tasks.
        }
        onDoubleClick(v);//Do what ever u want on Double Click
    }

    public abstract void onSingleClick(View v);

    public abstract void onDoubleClick(View v);
}

也可以被称为:

view.setOnClickListener(new DoubleClickListener() {

            @Override
            public void onSingleClick(View v) {

            }

            @Override
            public void onDoubleClick(View v) {

            }
        });

“Handler”比“TimerTask”更好。使用“handler.postDelayed()”。 - Suragch
如果您使用onSingleTapConfirmed(),编写所有这些单击延迟逻辑是不必要的。请参见此答案 - Suragch

8

如果您不想使用自定义视图,则可以采用以下方法。例如,ImageView。

// class level

GestureDetector gestureDetector;
boolean tapped;
ImageView imageView;

// inside onCreate of Activity or Fragment
gestureDetector = new GestureDetector(context,new GestureListener());

//--------------------------------------------------------------------------------

// 这是一段注释,用于分隔代码

public class GestureListener extends
        GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {

        return true;
    }

    // event when double tap occurs
    @Override
    public boolean onDoubleTap(MotionEvent e) {

        tapped = !tapped;

        if (tapped) {



        } else {



        }

        return true;
    }
}

//--------------------------------------------------------------------------------

对于 ImageView 的使用

imageView.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            return gestureDetector.onTouchEvent(event);
        }

    });

5

双击和单击

仅限双击

使用SimpleOnGestureListener可以很容易地检测视图上的双击(如Hannes Niederhausen's answer所示)。

private class GestureListener extends GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return true;
    }
}

我认为重新发明这个逻辑(例如bughi的答案)没有太大优势。

双击和单击延迟

您还可以使用SimpleOnGestureListener来区分单击和双击作为互斥事件。要做到这一点,只需覆盖onSingleTapConfirmed。这将延迟运行单击代码,直到系统确定用户没有双击(即,延迟> ViewConfiguration.getDoubleTapTimeout())。没有必要重新发明所有这些逻辑(就像thisthis和其他答案中所做的那样)。

private class GestureListener extends GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return true;
    }
}

双击和单击无延迟

onSingleTapConfirmed 的潜在问题是延迟。有时候明显的延迟是不可接受的。在这种情况下,您可以将 onSingleTapConfirmed 替换为 onSingleTapUp

private class GestureListener extends GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return true;
    }
}

你需要明白,无论是onSingleTapUp还是onDoubleTap都会在双击时被调用。(这基本上就是bughi's answer所做的和一些评论者抱怨的内容)。你可以使用延迟或同时调用两种方法。不可能没有延迟地进行单次轻敲并同时知道用户是否会再次轻敲。
如果单次轻敲延迟对你来说不可接受,则有几个选择:
  • Accept that both onSingleTapUp and onDoubleTap will be called for a double-tap. Just divide up your logic appropriately so that it doesn't matter. This is essentially what I did when I implemented a double-tap for caps-lock on a custom keyboard.
  • Don't use a double-tap. It's not an intuitive UI action for most things. As Dave Webb suggests, a long press is probably better. You can also implement that with the SimpleOnGestureListener:

    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
    
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }
    
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }
    
        @Override
        public void onLongPress(MotionEvent e) {
    
        }
    }
    

4

GuestureDetecter在大多数设备上运行良好,我想知道如何在双击事件上自定义两次点击之间的时间间隔,但我无法做到这一点。 我使用了“Bughi”“DoubleClickListner”更新了以上代码,并使用处理程序添加了一个定时器,在单击后延迟一段时间执行一段代码,如果双击在此延迟之前执行,则取消定时器和单击任务,并仅执行双击任务。 代码工作得很好,使其完美地用作双击侦听器:

  private Timer timer = null;  //at class level;
  private int DELAY   = 500;

  view.setOnClickListener(new DoubleClickListener() {

        @Override
        public void onSingleClick(View v) {

    final Handler  handler          = new Handler();
                final Runnable mRunnable        = new Runnable() {
                    public void run() {
                        processSingleClickEvent(v); //Do what ever u want on single click

                    }
                };

                TimerTask timertask = new TimerTask() {
                    @Override
                    public void run() {
                        handler.post(mRunnable);
                    }
                };
                timer   =   new Timer();
                timer.schedule(timertask, DELAY);       

        }

        @Override
        public void onDoubleClick(View v) {
                if(timer!=null)
                {
                 timer.cancel(); //Cancels Running Tasks or Waiting Tasks.
                 timer.purge();  //Frees Memory by erasing cancelled Tasks.
                }
              processDoubleClickEvent(v);//Do what ever u want on Double Click

        }
    });

1
干得好,但所有计时器代码都应该放在DoubleClickListener类的onClick()方法中,以避免在各处重复。 - bughi

4

3
boolean nonDoubleClick = true, singleClick = false;
        private long firstClickTime = 0L;
        private final int DOUBLE_CLICK_TIMEOUT = 200;

        listview.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
                // TODO Auto-generated method stub
                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        if (singleClick) {
                            Toast.makeText(getApplicationContext(), "Single Tap Detected", Toast.LENGTH_SHORT).show();
                        }
                        firstClickTime = 0L;
                        nonDoubleClick = true;
                        singleClick = false;
                    }
                }, 200);
                if (firstClickTime == 0) {
                    firstClickTime = SystemClock.elapsedRealtime();
                    nonDoubleClick = true;
                    singleClick = true;
                } else {
                    long deltaTime = SystemClock.elapsedRealtime() - firstClickTime;
                    firstClickTime = 0;
                    if (deltaTime < DOUBLE_CLICK_TIMEOUT) {
                        nonDoubleClick = false;
                        singleClick = false;
                        Toast.makeText(getApplicationContext(), "Double Tap Detected", Toast.LENGTH_SHORT).show();
                    }
                }

            }
        });

3
改良版的Dhruvi代码
public abstract class DoubleClickListener implements View.OnClickListener {

private static final long DOUBLE_CLICK_TIME_DELTA = 300;//milliseconds

long lastClickTime = 0;
boolean tap = true;

@Override
public void onClick(View v) {
    long clickTime = System.currentTimeMillis();
    if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA){
        onDoubleClick(v);
        tap = false;
    } else
        tap = true;

    v.postDelayed(new Runnable() {
        @Override
        public void run() {
            if(tap)
                onSingleClick();
        }
    },DOUBLE_CLICK_TIME_DELTA);

    lastClickTime = clickTime;
}

public abstract void onDoubleClick(View v);

public abstract void onSingleClick();
}

简单易懂,谢谢! - tm1701

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接