Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Button with dual short and continuous press?

I'm creating an MP3 player and want a dual Next Song / Fast Forward button. So if this button is pressed it will move on to the next song, and if it is held down it will fast forward through the current song.

I can get the Next Song working using a OnClickListener...

private OnClickListener mSkipForwardListener = new OnClickListener() {
    public void onClick(View v)
    {
        mPlayerService.forwardASong();
    }
};

...but how do I get the fast forward functionality ? I tried OnLongClickListener but that only fires once.

private OnLongClickListener mFastForwardListener = new OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        mPlayerService.fastForward();
        return true;
    }
};

And onTouch only seems to fire once on the key.down and key.up.

private OnTouchListener mFastForwardListener = new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mPlayerService.fastForward();
        return true;
    }
};

Any help, much appreciated, M.

like image 521
MickeyR Avatar asked May 06 '11 23:05

MickeyR


People also ask

What is the difference between long press and short press buttons?

The answer is simple. The long press function is triggered whilst the button is being pressed, the short press function is triggered once the button is released. This can again be observed on a smart phone by releasing an object on screen just before the long press function activates.

How do we trigger the long press without triggering the short press?

The second problem is the long press function. How do we trigger this function without triggering the short press function first? The answer is simple. The long press function is triggered whilst the button is being pressed, the short press function is triggered once the button is released.

How do you do multiple tasks with a button press?

If there are only two functions, we can use the long press and short press to do the two seperate tasks. If there are two or more functions, we can use the button press's count to do multiple tasks.

What is a push button like this called?

Many of us use a button like this, on smartphones for example, everyday; this is called a short press and a long press (press and hold). The following guide will take you through all the steps needed to create a simple push button that can control the state of two separate LED's.


2 Answers

By returning true from onTouch you are consuming the touch event so your button never even sees it. The reason no more touch events occur is that no View actually handled the down event.

So you need to return false from onTouch in your listener. (And hope the underlying view keeps returning true, because if the View - button in this case - returns false from it's onTouchEvent then no more events will be sent to your listener either - for a button this is fine, but for other views override onTouchEvent instead of using a listener for more control and reliability).

Something like the following OnTouchListener should be roughly right (this will need to be an inner class of an Activity, and don't set an OnClickListener as well because it will also be called!):

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

And here's an example of an implementation

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);
    }
}
like image 105
Joseph Earl Avatar answered Oct 02 '22 12:10

Joseph Earl


Inspired by the above suggested code, you can make one or more buttons continuously execute its action periodically while it is still pressed, using the Activity's Message Handler, but in a simpler way than suggested above (without introducing private abstract extended classes). It is also simple to do a different action during a short press than during a continuous press, with a slight variation from what I show below.

In the button initialization, implement both the onClick and onTouch methods:

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

The Activity's message Handler could be:

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;
        } 
    }
};
like image 24
Markwell Avatar answered Oct 02 '22 13:10

Markwell