#android #button #onclick #ontouchlistener
#Android #кнопка #onclick #ontouchlistener
Вопрос:
Я создаю MP3-плеер и хочу иметь двойную кнопку следующей песни / быстрой перемотки вперед. Итак, если нажать на эту кнопку, она перейдет к следующей песне, а если ее удерживать нажатой, произойдет быстрая перемотка вперед через текущую песню.
Я могу запустить следующую песню, используя OnClickListener…
private OnClickListener mSkipForwardListener = new OnClickListener() {
public void onClick(View v)
{
mPlayerService.forwardASong();
}
};
… но как мне получить функцию быстрой перемотки вперед? Я попробовал OnLongClickListener, но это срабатывает только один раз.
private OnLongClickListener mFastForwardListener = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mPlayerService.fastForward();
return true;
}
};
И onTouch, похоже, срабатывает только один раз по клавише.вниз и клавиша.вверх.
private OnTouchListener mFastForwardListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mPlayerService.fastForward();
return true;
}
};
Любая помощь, высоко ценится,
M.
Комментарии:
1. Учитывая подход OnTouchListener, учитывая тот факт, что я, возможно, упускаю здесь что-то очевидное, разве это не именно то, что вы хотите? Как только пользователь нажимает кнопку, вы получаете событие ACTION_DOWN. Затем вы запускаете таймер, который определяет, выбирать ли следующую песню или запускать быструю перемотку. Если вы получите ACTION_UP до истечения этого таймера, это следующая песня. В противном случае после превышения таймера вы запускаете быструю переадресацию до тех пор, пока не получите ACTION_UP. Мультитач может выявить некоторую сложность, о которой я не могу сейчас думать, но это то, с чего я начал.
2. Вы в значительной степени правы. Моя единственная трудность заключается во второй части того, что вы говорите. После истечения таймера я не получу ACTION_UP, потому что кнопка останется нажатой, и должна произойти быстрая переадресация.
3. Воспринимайте это как то, что кто-то пишет поздно вечером в пятницу (точнее, ночью). Я думал об использовании потока, например, в качестве таймера. В этом потоке вы можете подождать, скажем, 200 мс, и после ожидания этого времени он вызовет startFastForward, если раньше не было ACTION_UP, что приведет к следующей песне. И эта конкретная часть моего комментария должна гласить «начать быструю переадресацию
until you receive ACTION_UP
«.
Ответ №1:
Возвращаясь true
из onTouch
, вы используете событие касания, поэтому ваша кнопка даже не видит его. Причина, по которой больше не происходят события касания, заключается в том, что ни одно представление фактически не обработало событие down.
Итак, вам нужно вернуться false
из onTouch
вашего слушателя. (И надеюсь, что базовое представление продолжает возвращаться true
, потому что, если View — button в данном случае — возвращает false из своего onTouchEvent
, то вашему слушателю также больше не будут отправляться события — для кнопки это нормально, но для других представлений переопределите onTouchEvent
вместо использования прослушивателя для большего контроля и надежности).
Что-то вроде следующего OnTouchListener
должно быть примерно правильным (это должен быть внутренний класс Activity, и не устанавливайте OnClickListener
также, потому что он также будет вызван!):
private abstract class LongTouchActionListener implements OnTouchListener {
/**
* Implement these methods in classes that extend this
*/
public abstract void onClick(View v);
public abstract void onLongTouchAction(View v);
/**
* The time before we count the current touch as
* a long touch
*/
public static final long LONG_TOUCH_TIME = 500;
/**
* The interval before calling another action when the
* users finger is held down
*/
public static final long LONG_TOUCH_ACTION_INTERVAL = 100;
/**
* The time the user first put their finger down
*/
private long mTouchDownTime;
/**
* The coordinates of the first touch
*/
private float mTouchDownX;
private float mTouchDownY;
/**
* The amount the users finger has to move in DIPs
* before we cancel the touch event
*/
public static final int TOUCH_MOVE_LIMIT_DP = 50;
/**
* TOUCH_MOVE_LIMIT_DP converted to pixels, and squared
*/
private float mTouchMoveLimitPxSq;
/**
* Is the current touch event a long touch event
*/
private boolean mIsLongTouch;
/**
* Is the current touch event a simple quick tap (click)
*/
private boolean mIsClick;
/**
* Handlers to post UI events
*/
private LongTouchHandler mHandler;
/**
* Reference to the long-touched view
*/
private View mLongTouchView;
/**
* Constructor
*/
public LongTouchActionListener(Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
mTouchMoveLimitPxSq = scale*scale*TOUCH_MOVE_LIMIT_DP*TOUCH_MOVE_LIMIT_DP;
mHandler = new LongTouchHandler();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// down event
mIsLongTouch = false;
mIsClick = true;
mTouchDownX = event.getX();
mTouchDownY = event.getY();
mTouchDownTime = event.getEventTime();
mLongTouchView = view;
// post a runnable
mHandler.setEmptyMessageDelayed(LongTouchHandler.MESSAGE_LONG_TOUCH_WAIT, LONG_TOUCH_TIME);
break;
case MotionEvent.ACTION_MOVE:
// check to see if the user has moved their
// finger too far
if (mIsClick || mIsLongTouch) {
final float xDist = (event.getX() - mTouchDownX);
final float yDist = (event.getY() - mTouchDownY);
final float distanceSq = (xDist*xDist) (yDist*yDist);
if (distanceSq > mTouchMoveLimitSqPx) {
// cancel the current operation
mHandler.removeMessages(LongTouchHandler.MESSAGE_LONG_TOUCH_WAIT);
mHandler.removeMessages(LongTouchHandler.MESSAGE_LONG_TOUCH_ACTION);
mIsClick = false;
mIsLongTouch = false;
}
}
break;
case MotionEvent.ACTION_CANCEL:
mIsClick = false;
case MotionEvent.ACTION_UP:
// cancel any message
mHandler.removeMessages(LongTouchHandler.MESSAGE_LONG_TOUCH_WAIT);
mHandler.removeMessages(LongTouchHandler.MESSAGE_LONG_TOUCH_ACTION);
long elapsedTime = event.getEventTime() - mTouchDownTime;
if (mIsClick amp;amp; elapsedTime < LONG_TOUCH_TIME) {
onClick(v);
}
break;
}
// we did not consume the event, pass it on
// to the button
return false;
}
/**
* Handler to run actions on UI thread
*/
private class LongTouchHandler extends Handler {
public static final int MESSAGE_LONG_TOUCH_WAIT = 1;
public static final int MESSAGE_LONG_TOUCH_ACTION = 2;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_LONG_TOUCH_WAIT:
mIsLongTouch = true;
mIsClick = false;
// flow into next case
case MESSAGE_LONG_TOUCH_ACTION:
if (!mIsLongTouch) return;
onLongTouchAction(mLongTouchView); // call users function
// wait for a bit then update
takeNapThenUpdate();
break;
}
}
private void takeNapThenUpdate() {
sendEmptyMessageDelayed(MESSAGE_LONG_TOUCH_ACTION, LONG_TOUCH_ACTION_INTERVAL);
}
};
};
И вот пример реализации
private class FastForwardTouchListener extends LongTouchActionListener {
public void onClick(View v) {
// Next track
}
public void onLongTouchAction(View v) {
// Fast forward the amount of time
// between long touch action calls
mPlayer.seekTo(mPlayer.getCurrentPosition() LONG_TOUCH_ACTION_INTERVAL);
}
}
Комментарии:
1. Спасибо за это! Выглядит именно так, как мне нужно. Однако вопрос: что такое объект mWaiter в коде? M
2. Извинения, которые должны были быть
mHandler
, я изменил имена при написании и забыл изменить это. Исправлено.3. Отличный ответ! Два исправления ошибок, которые я нашел в вашем коде: 1. используйте sendEmptyMessageDelayed, а не setEmptyMessageDelayed. 2. У вас опечатка: mTouchMoveLimitPxSq, а не mTouchMoveLimitSqPx.
Ответ №2:
Вдохновленный предложенным выше кодом, вы можете заставить одну или несколько кнопок непрерывно периодически выполнять свое действие, пока она все еще нажата, используя обработчик сообщений Activity, но более простым способом, чем предложено выше (без введения частных абстрактных расширенных классов). Также просто выполнить другое действие при коротком нажатии, чем при непрерывном, с небольшим отличием от того, что я показываю ниже.
При инициализации кнопки реализуйте оба метода onClick и onTouch:
myButton = (Button) findViewById(R.id.MyButton);
myButton.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View arg0) {
Log.i("myBtn", "Clicked ");
// perform click / short press action
}
}
);
myButton.setOnTouchListener(
new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("myBtn", "Btn Down");
v.performClick(); // call the above onClick handler now if appropriate
// Make this a repeating button, using MessageHandler
Message msg = new Message();
msg.what = MESSAGE_CHECK_BTN_STILL_PRESSED;
msg.arg1 = R.id.MyButton;
msg.arg2 = 250; // this btn's repeat time in ms
v.setTag(v); // mark btn as pressed (any non-null)
myGuiHandler.sendMessageDelayed(msg, msg.arg2);
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
v.setTag(null); // mark btn as not pressed
break;
}
return true; // return true to prevent calling btn onClick handler
}
}
);
Обработчик сообщений Activity может быть:
public static final int MESSAGE_CHECK_BTN_STILL_PRESSED = 1;
public final Handler myGuiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CHECK_BTN_STILL_PRESSED:
Button btn = (Button) findViewById(msg.arg1);
if (btn.getTag() != null) { // button is still pressed
Log.i("myBtn", "still pressed");
btn.performClick(); // perform Click or different long press action
Message msg1 = new Message(); // schedule next btn pressed check
msg1.copyFrom(msg);
myGuiHandler.sendMessageDelayed(msg1, msg1.arg2);
}
break;
}
}
};