Android: Comment détecter le double-toucher?

J’ai un problème avec la mise en œuvre du double tap. Eh bien j’ai implémenté le onGestureListener et j’ai eu le gestureDetector , mais je ne suis pas sûr où est le problème, voici mon 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 Ssortingng 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().toSsortingng(); 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{ Ssortingng 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, une autre question, si mon ListView a un onItemClickListener , est-ce que Android peut détecter entre une simple pression ou une double pression?

Pourquoi n’utilisez-vous pas une presse longue? Ou utilisez-vous déjà cela pour autre chose? Les avantages d’un appui long sur un double contact:

  • Long Press est une interaction recommandée dans les directives de l’interface utilisateur, Double Touch ne l’est pas.
  • C’est ce que les utilisateurs attendent; un utilisateur pourrait ne pas trouver d’action Double Touch car il ne le cherchera pas
  • Il est déjà géré dans l’API .
  • L’implémentation d’un double contact affectera le traitement des touches simples, car vous devrez attendre pour voir si chaque simple touche devient un double contact avant de pouvoir le traiter.

Vous pouvez utiliser le GestureDetector. Voir le code suivant:

 public class MyView extends View { GestureDetector gestureDetector; public MyView(Context context, AtsortingbuteSet 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; } } } 

Vous pouvez remplacer d’autres méthodes de l’auditeur pour obtenir des taps simples, des flinges, etc.

Comme alternative légère à GestureDetector, vous pouvez utiliser cette classe

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

Exemple:

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

en combinant “Bughi” “DoubleClickListner” et “Jayant Arora” Timer dans une classe contenue:

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

et peut être appelé comme:

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

Si vous ne souhaitez pas utiliser l’affichage personnalisé, vous pouvez utiliser l’approche suivante. par exemple 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; } } 

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

pour ImageView

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

Ceci est ma solution, il utilise par défaut setOnItemClickListener() . J’ai eu la même tâche à mettre en œuvre. Bientôt, je posterai un exemple et un cours personnalisé sur mon github. Une brève explication est donnée. Je ne suis pas sûr que le temps en millisecondes soit la bonne différence pour le système (voir la source de ViewConfiguration.getDoubleTapTimeout() ) pour choisir entre simple et double tap.

Edit: Voir ici: https://github.com/NikolaDespotoski/DoubleTapListView ou https://github.com/NikolaDespotoski/DoubleTapListViewHandler

GuestureDetecter fonctionne bien sur la plupart des périphériques, je voudrais savoir comment le temps entre deux clics peut être personnalisé sur un événement de double clic, je n’ai pas pu le faire. J’ai mis à jour le code ci-dessus par “Bughi” “DoubleClickListner”, a ajouté un temporisateur utilisant un gestionnaire qui exécute un code après un délai spécifique en un clic et annule le temporisateur et la tâche en un seul clic double clic sur la tâche. Le code fonctionne bien Le rend parfait pour une utilisation en double clic:

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

Code dhruvi improvisé

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

Ma solution peut être utile.

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

Réalisation simple et double clic

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

Solution de Bughi & Jayant Arora pour le 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);} 

Code C # équivalent que j’ai utilisé pour implémenter les mêmes fonctionnalités et que je peux même personnaliser pour accepter un nombre N de tâches

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

Actuellement, le code ci-dessus accepte 7 comme nombre de taps avant qu'il déclenche l'événement View Tapped. Mais il peut être personnalisé avec n'importe quel nombre

Appuyez deux fois et appuyez une fois sur

Appuyez deux fois seulement

Il est assez facile de détecter un double SimpleOnGestureListener sur une vue en utilisant SimpleOnGestureListener (comme le montre la réponse de Hannes Niederhausen ).

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

Je ne vois pas le grand avantage de réinventer la logique pour cela (comme la réponse de Bughi ).

Appuyez deux fois et appuyez une fois avec retard

Vous pouvez également utiliser SimpleOnGestureListener pour différencier un simple SimpleOnGestureListener et un double- SimpleOnGestureListener en tant SimpleOnGestureListener mutuellement exclusifs. Pour ce faire, il vous suffit de remplacer sur onSingleTapConfirmed . Cela retardera l’exécution du code simple jusqu’à ce que le système soit certain que l’utilisateur n’a pas double-tapé (c’est-à-dire le délai> ViewConfiguration.getDoubleTapTimeout() ). Il n’y a aucune raison de réinventer toute la logique pour cela (comme cela est fait dans ceci , ceci et d’autres réponses).

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

Appuyez deux fois et appuyez une seule fois sans retard

Le problème potentiel avec onSingleTapConfirmed est le délai. Parfois, un retard notable n’est pas acceptable. Dans ce cas, vous pouvez remplacer onSingleTapConfirmed par 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; } } 

Vous devez toutefois vous rendre compte que onSingleTapUp et onDoubleTap seront tous deux appelés si vous les appuyez deux fois. (C’est essentiellement ce que la réponse de bughi fait et ce que certains commentateurs se plaignaient). Vous devez soit utiliser le délai, soit appeler les deux méthodes. Il n’est pas possible d’avoir un seul clic sans délai et en même temps savoir si l’utilisateur va appuyer à nouveau.

Si le délai d’un seul clic n’est pas acceptable pour vous, vous avez deux options:

  • Acceptez le fait que onSingleTapUp et onDoubleTap seront tous deux appelés pour un double onDoubleTap . Il suffit de diviser votre logique de manière appropriée afin que cela n’ait pas d’importance. C’est essentiellement ce que j’ai fait lorsque j’ai implémenté un double-tap pour verrouiller les majuscules sur un clavier personnalisé.
  • N’utilisez pas un double-clic. Ce n’est pas une action intuitive de l’interface pour la plupart des choses. Comme Dave Webb le suggère , une longue pression est probablement meilleure. Vous pouvez également implémenter cela avec 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) { } }