Android: Как обнаружить двойное нажатие?
у меня есть проблема с реализацией двойное нажатие. Ну я реализовал onGestureListener
и я gestureDetector
, но я не уверен, где проблема, вот мой код:
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 обнаружить между одним нажатием или двойным нажатием для него?
15 ответов:
Почему вы не используете длительное нажатие? Или вы уже используете это для чего-то другого? Преимущества длительного нажатия над двойным касанием:
- длительное нажатие-это рекомендуемое взаимодействие в рекомендациях пользовательского интерфейса, двойное касание-нет.
- это то, что пользователи ожидают; пользователь не может найти двойное действие касания, поскольку они не будут искать его
- уже обрабатывается в API.
- реализация двойного касания повлияет обработка одиночных касаний, потому что вам придется подождать, чтобы увидеть, если каждое прикосновение превращается в двойное касание, прежде чем вы можете обработать его.
вы можете использовать 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; } } }
вы можете переопределить другие методы прослушивателя, чтобы получить одиночные нажатия, флинги и так далее.
в качестве легкой альтернативы 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) { } });
объединение таймера" 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) { } });
Если вы не хотите, чтобы перейти на пользовательский вид, то вы можете использовать следующий подход. например, книги
// 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); } });
Это мое решение, оно использует значение по умолчанию
setOnItemClickListener()
. У меня была такая же задача для реализации. Скоро я опубликую пример и пользовательский класс на своем github. Дается краткое объяснение. Я не уверен, что время в миллисекундах-это правильная разница для системы (см.ViewConfiguration.getDoubleTapTimeout()
источник), чтобы выбрать между одним и двойным нажатием.Edit: Смотрите здесь: https://github.com/NikolaDespotoski/DoubleTapListView или https://github.com/NikolaDespotoski/DoubleTapListViewHandler
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 } });
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(); } } } });
самодельные 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(); }
мое решение, может быть полезно.
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; } }
реализация одиночный и двойной щелчок
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; } } }
решение от bughi & Jayant Arora для 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);}
эквивалентный код C#, который я использовал для реализации той же функциональности и даже может настроить, чтобы принять N количество нажатий
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; } } }
В настоящее время выше код принимает 7 в качестве количества нажатий, прежде чем он поднимает вид постучал событие. Но он может быть настроен с любым количеством
дважды нажмите и один-нажмите
дважды нажмите только
это довольно легко обнаружить двойное нажатие на просмотреть с помощью
SimpleOnGestureListener
(как показано в Ханнес Нидернхаузена по).private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onDoubleTap(MotionEvent e) { return true; } }
я не вижу большого преимущества для повторного изобретения логики для этого (например ответ баги).
дважды нажмите и один кран с задержкой
вы можете также используйте
SimpleOnGestureListener
чтобы различать одно касание и двойное нажатие как взаимоисключающие события. Для этого нужно просто переопределитьonSingleTapConfirmed
. Это задержит выполнение кода с одним касанием до тех пор, пока система не будет уверена, что пользователь не нажал дважды (т. е. задержка >ViewConfiguration.getDoubleTapTimeout()
). Определенно нет причин заново изобретать всю логику для этого (как это делается в этой,этой и другие ответы).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
будет вызван, если есть двойное нажатие. (Это по существу то, что ответ баги и то, что некоторые комментаторы жаловались.) Вы либо должны использовать задержка или вызов обоих методов. Невозможно иметь одно касание без задержки и в то же время знать, собирается ли пользователь снова нажать.если задержка однократного нажатия не приемлема для вас, то у вас есть несколько вариантов:
- примите это как
onSingleTapUp
иonDoubleTap
будет вызван для двойного нажатия. Просто разделите свою логику соответствующим образом, чтобы это не имело значения. Это по существу то, что я сделал, когда я реализовал двойной кран для caps-lock на пользовательской клавиатуре.не используйте двойное касание. Это не интуитивное действие пользовательского интерфейса для большинства вещей. Как Дэйв Уэбб предлагает, и, наверное, лучше. Вы также можете реализовать это с помощью тега
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) { } }