Android: Wie erkenne ich Doppeltippen?

Ich habe ein Problem mit der Implementierung von Doppeltippen. Nun, ich habe den gestureDetector implementiert und ich hatte den gestureDetector , aber ich bin mir nicht sicher, wo das Problem ist, hier ist mein Code:

  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 sResultsArr = new ArrayList(); 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 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; } 

Oh, eine andere Frage, wenn mein ListView einen onItemClickListener , kann android zwischen einem Tippen oder einem Doppeltippen erkennen?

   

Warum benutzt du keine lange Presse? Oder benutzt du das schon für etwas anderes? Die Vorteile eines Long Press über einen Double Touch:

  • Long Press ist eine empfohlene Interaktion in den UI-Richtlinien, Double Touch ist dies nicht.
  • Das erwarten die Nutzer. Ein Nutzer findet möglicherweise keine Double Touch-Aktion, da er nicht danach sucht
  • Es ist bereits in der API behandelt .
  • Die Implementierung eines Double Touch beeinflusst die Handhabung von Single Touches, da Sie warten müssen, um zu sehen, ob jeder Single Touch zu einem Double Touch wird, bevor Sie ihn verarbeiten können.

Sie können den GestureDetector verwenden. Siehe den folgenden Code:

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

Sie können andere Methoden des Listeners überschreiben, um einzelne Taps, Flackern usw. zu erhalten.

Als leichte Alternative zu GestureDetector können Sie diese class verwenden

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

Beispiel:

  view.setOnClickListener(new DoubleClickListener() { @Override public void onSingleClick(View v) { } @Override public void onDoubleClick(View v) { } }); 

Kombination von “Bughi” “DoubleClickListner” und “Jayant Arora” Timer in einer enthaltenen class:

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

und kann aufgerufen werden als:

 view.setOnClickListener(new DoubleClickListener() { @Override public void onSingleClick(View v) { } @Override public void onDoubleClick(View v) { } }); 

Wenn Sie keine benutzerdefinierte Ansicht verwenden möchten, können Sie den folgenden Ansatz verwenden. zB 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; } } 

// ———————————————— ——————————–

für ImageView

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

Dies ist meine Lösung, es verwendet Standard setOnItemClickListener() . Ich hatte die gleiche Aufgabe zu implementieren. Bald werde ich Beispiel und benutzerdefinierte class auf meinem GitHub veröffentlichen. Kurze Erklärung wird gegeben. Ich bin mir nicht sicher, ob die Zeit in Millisekunden der richtige Unterschied für das System ist (siehe Quelle ViewConfiguration.getDoubleTapTimeout() ), um zwischen Einzel- und Doppeltippen zu entscheiden.

Bearbeiten: Sehen Sie es hier: https://github.com/NikolaDespotoski/DoubleTapListView oder https://github.com/NikolaDespotoski/DoubleTapListViewHandler

GuestureDetecter functioniert gut auf den meisten Geräten. Ich würde gerne wissen, wie die Zeit zwischen zwei Klicks bei einem Doppelklick-Ereignis angepasst werden kann, das war nicht möglich. Ich aktualisierte den obigen Code mit “Bughi” “DoubleClickListner”, fügte einen Timer mit Handler hinzu, der einen Code nach einer bestimmten Verzögerung mit einem einzigen Klick ausführt, und wenn ein Doppelklick vor dieser Verzögerung ausgeführt wird, bricht er den Timer- und Einzelklick-Task ab und führt ihn nur aus Doppelklick-Aufgabe. Der Code funktioniert. Fein macht es perfekt als Doppelklick-Listener zu verwenden:

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

Improvisierter Dhruvi-Code

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

Meine Lösung kann hilfreich sein.

 long lastTouchUpTime = 0; boolean isDoubleClick = false; private void performDoubleClick() { long currentTime = System.currentTimeMillis(); if(!isDoubleClick && currentTime - lastTouchUpTime < DOUBLE_CLICK_TIME_INTERVAL) { isDoubleClick = true; lastTouchUpTime = currentTime; Toast.makeText(context, "double click", Toast.LENGTH_SHORT).show(); } else { lastTouchUpTime = currentTime; isDoubleClick = false; } } 

Realisierung Einzel- und Doppelklick

 public abstract class DoubleClickListener implements View.OnClickListener { private static final long DOUBLE_CLICK_TIME_DELTA = 200; private long lastClickTime = 0; private View view; private Handler handler = new Handler(); private Runnable runnable = new Runnable() { @Override public void run() { onSingleClick(view); } }; private void runTimer(){ handler.removeCallbacks(runnable); handler.postDelayed(runnable,DOUBLE_CLICK_TIME_DELTA); } @Override public void onClick(View view) { this.view = view; long clickTime = System.currentTimeMillis(); if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA){ handler.removeCallbacks(runnable); lastClickTime = 0; onDoubleClick(view); } else { runTimer(); lastClickTime = clickTime; } } public abstract void onSingleClick(View v); public abstract void onDoubleClick(View v); 

}

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

Lösung von Bughi & Jayant Arora für CopyPast:

 public abstract class DoubleClickListener implements View.OnClickListener { private int position; private Timer timer; private static final long DOUBLE_CLICK_TIME_DELTA = 300;//milliseconds long lastClickTime = 0; public DoubleClickListener (int position) { this.position = position; } @Override public void onClick(View v) { long clickTime = System.currentTimeMillis(); if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA){ if (timer != null) { timer.cancel(); //Cancels Running Tasks or Waiting Tasks. timer.purge(); //Frees Memory by erasing cancelled Tasks. } onDoubleClick(v, position); } else { final Handler handler = new Handler(); final Runnable mRunnable = () -> { onSingleClick(v, position); }; TimerTask timertask = new TimerTask() { @Override public void run() { handler.post(mRunnable); } }; timer = new Timer(); timer.schedule(timertask, DOUBLE_CLICK_TIME_DELTA); } lastClickTime = clickTime; } public abstract void onSingleClick(View v, int position); public abstract void onDoubleClick(View v, int position);} 

Äquivalenter C # -Code, den ich verwendet habe, um die gleiche functionalität zu implementieren und kann sogar anpassen, um N Anzahl von Taps zu akzeptieren

 public interface IOnTouchInterface { void ViewTapped(); } public class MultipleTouchGestureListener : Java.Lang.Object, View.IOnTouchListener { int clickCount = 0; long startTime; static long MAX_DURATION = 500; public int NumberOfTaps { get; set; } = 7; readonly IOnTouchInterface interfc; public MultipleTouchGestureListener(IOnTouchInterface tch) { this.interfc = tch; } public bool OnTouch(View v, MotionEvent e) { switch (e.Action) { case MotionEventActions.Down: clickCount++; if(clickCount == 1) startTime = Utility.CurrentTimeSince1970; break; case MotionEventActions.Up: var currentTime = Utility.CurrentTimeSince1970; long time = currentTime - startTime; if(time < = MAX_DURATION * NumberOfTaps) { if (clickCount == NumberOfTaps) { this.interfc.ViewTapped(); clickCount = 0; } } else { clickCount = 0; } break; } return true; } } public static class Utility { public static long CurrentTimeSince1970 { get { DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local); DateTime dtNow = DateTime.Now; TimeSpan result = dtNow.Subtract(dt); long seconds = (long)result.TotalMilliseconds; return seconds; } } } 

Der obige Code akzeptiert 7 als Anzahl der Taps, bevor das Tapped-Ereignis "View" ausgetriggers wird. Aber es kann mit einer beliebigen Nummer angepasst werden

Doppeltippen und Einmaltippen

Nur zweimal tippen

Mit SimpleOnGestureListener ( SimpleOnGestureListener Hannes Niederhausens Antwort ) ist es sehr einfach, einen Doppelklick auf eine Ansicht zu erkennen.

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

Ich kann keinen großen Vorteil darin sehen, die Logik dafür neu zu erfinden (wie Bughis Antwort ).

Doppeltippen und Einzeltippen mit Verzögerung

Sie können auch den SimpleOnGestureListener um zwischen Einzel- und Doppeltipp als sich gegenseitig ausschließende Ereignisse zu unterscheiden. Um dies zu tun, überschreiben Sie onSingleTapConfirmed . Dies verzögert die Ausführung des Single-Tap-Codes, bis das System sicher ist, dass der Benutzer nicht zweimal ViewConfiguration.getDoubleTapTimeout() hat (dh die Verzögerung> ViewConfiguration.getDoubleTapTimeout() ). Es gibt definitiv keinen Grund, die ganze Logik dafür neu zu erfinden (wie in dieser , dieser und anderen Antworten).

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

Doppeltippen und Einmaltippen ohne Verzögerung

Das mögliche Problem mit onSingleTapConfirmed ist die Verzögerung. Manchmal ist eine spürbare Verzögerung nicht akzeptabel. In diesem Fall können Sie onSingleTapConfirmed durch onSingleTapUp ersetzen.

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

Sie müssen jedoch onSingleTapUp , dass sowohl onSingleTapUp als auch onDoubleTap aufgerufen werden, wenn ein Doppeltippen erfolgt. (Dies ist im Wesentlichen die Antwort von Bughi und was einige der Kommentatoren beschweren.) Sie müssen entweder die Verzögerung verwenden oder beide Methoden aufrufen. Es ist nicht möglich, einen einzigen Tipp ohne Verzögerung auszuführen und gleichzeitig zu wissen, ob der Benutzer erneut klopft.

Wenn die Einzeltippverzögerung für Sie nicht akzeptabel ist, haben Sie mehrere Optionen:

  • Akzeptieren Sie, dass sowohl onSingleTapUp als auch onDoubleTap für einen Doppeltipp onDoubleTap werden. Teilen Sie Ihre Logik einfach so auf, dass es keine Rolle spielt. Dies ist im Wesentlichen, was ich getan habe, als ich eine Doppel-Tap für Caps-Lock auf einer benutzerdefinierten Tastatur implementiert.
  • Verwenden Sie keinen Doppeltipp. Es ist keine intuitive UI-Aktion für die meisten Dinge. Wie Dave Webb vorschlägt , ist ein langes Drücken wahrscheinlich besser. Sie können dies auch mit dem 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) { } }