android - studio - ¿Cómo implementar el botón con doble pulsación corta y continua?
efectos botones android studio (2)
Estoy creando un reproductor de MP3 y quiero un botón dual Siguiente canción / Avance rápido. Entonces, si se presiona este botón, pasará a la siguiente canción y, si se mantiene presionada, avanzará rápidamente a través de la canción actual.
Puedo hacer funcionar la próxima canción usando OnClickListener ...
private OnClickListener mSkipForwardListener = new OnClickListener() {
public void onClick(View v)
{
mPlayerService.forwardASong();
}
};
... pero ¿cómo obtengo la funcionalidad de avance rápido? Probé OnLongClickListener pero eso solo se dispara una vez.
private OnLongClickListener mFastForwardListener = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mPlayerService.fastForward();
return true;
}
};
Y onTouch solo parece disparar una vez en la tecla. Abajo y tecla.up.
private OnTouchListener mFastForwardListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mPlayerService.fastForward();
return true;
}
};
Cualquier ayuda, muy apreciada, M.
Inspirado por el código sugerido anteriormente, puede hacer que uno o más botones ejecuten su acción periódicamente mientras todavía está presionado, usando el Manejador de mensajes de la Actividad, pero de una manera más simple que la sugerida anteriormente (sin introducir clases privadas abstractas ampliadas). También es simple hacer una acción diferente durante una pulsación corta que durante una prensa continua, con una ligera variación de lo que muestro a continuación.
En la inicialización del botón, implemente los métodos onClick y 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
}
}
);
El controlador de mensajes de la actividad podría ser:
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;
}
}
};
Al volverse true
desde onTouch
, está consumiendo el evento táctil, por lo que su botón ni siquiera lo ve. La razón por la que no ocurren más eventos táctiles es que ninguna Vista realmente manejó el evento hacia abajo.
Por lo tanto, debe devolver false
de onTouch
en su oyente. (Y espero que la vista subyacente siga siendo true
, porque si en este caso el botón Ver devuelve falso desde el onTouchEvent
, onTouchEvent
se onTouchEvent
más eventos al oyente; para un botón, está bien, pero para otras vistas se anula el onTouchEvent
en lugar de usar un oyente para mayor control y confiabilidad).
Algo así como el siguiente OnTouchListener
debería ser más o menos correcto (será necesario que sea una clase interna de una actividad y no configure OnClickListener
porque también se llamará).
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 && 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);
}
};
};
Y aquí hay un ejemplo de una implementación
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);
}
}