如何实现具有短按和长按两种功能的按钮?

5

我正在创建一个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。
任何帮助都将不胜感激。

考虑到OnTouchListener方法,鉴于我可能会错过一些显而易见的东西,这难道不是你想要的吗?一旦用户按下按钮,您将收到一个ACTION_DOWN事件。然后启动一个计时器,该计时器决定是选择下一首歌曲还是开始快进。如果在此计时器超过之前收到ACTION_UP,则为下一首歌曲。否则,在计时器超过后,只要收到ACTION_UP,就开始快进。多点触控可能会揭示一些我现在无法想到的复杂性,但这是我开始使用的内容。 - harism
你说得基本正确。我唯一的困难在于你所说的第二部分。当计时器超过后,我将不会收到 ACTION_UP 事件,因为按钮将保持按下状态,快进操作必须继续进行。 - MickeyR
把这个当作周五晚上(更确切地说是夜晚)写的东西。我的想法是使用一个线程,例如作为计时器。在该线程中,您可以等待,比如200毫秒,等待时间结束后,如果之前没有ACTION_UP,则会调用startFastForward,这将导致播放下一首歌曲。而我评论中特别的那部分应该写成“开始快进,直到收到ACTION_UP”。 - harism
2个回答

6
通过从onTouch返回true来消耗触摸事件,因此您的按钮甚至无法看到它。没有更多的触摸事件发生的原因是没有View实际处理down事件。
因此,在您的侦听器中需要从onTouch返回false。(并希望底层视图继续返回true,因为如果视图 - 在这种情况下为按钮 - 从其onTouchEvent返回false,则不会向您的侦听器发送更多事件 - 对于按钮而言,这是可以接受的,但对于其他视图,请重写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 && 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);
    }
}

谢谢!看起来就是我需要的东西。不过有个问题:代码中的mWaiter对象是什么?M - MickeyR
抱歉,应该是 mHandler,我在编写时更改了名称,但忘记更改它。现已修复。 - Joseph Earl
很棒的答案!我在你的代码中发现了两个错误:1. 使用sendEmptyMessageDelayed而不是setEmptyMessageDelayed。2. 你有一个拼写错误:mTouchMoveLimitPxSq而不是mTouchMoveLimitSqPx。 - limlim

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

活动的消息处理程序可以是:
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;
        } 
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接