LongClick event happens too quickly. How can I increase the clicktime required to trigger it?

后端 未结 8 1424
暖寄归人
暖寄归人 2020-12-08 21:26

In an application I\'m working on, I have the requirement that a user must click & hold a component for a period time before a certain action occurs.

I\'m curren

相关标签:
8条回答
  • 2020-12-08 22:04

    I found a simple solution studying how the long press event works. Each time a view is clicked, a Runnable of type CheckForLongPress is added to a queue with a delay. If the delay ends, the OnLongClickListener is called. If there is a different event before the delay ends, then, the CheckForLongPress Runnable is removed from de queue.

    I just override the public method postDelayed(Runnable action, long delayMillis) of the view to change the OS delay

    @Override public boolean postDelayed(Runnable action, long delayMillis) {
        boolean isLongPress = action.getClass().getSimpleName().equals("CheckForLongPress");
        return super.postDelayed(action, isLongPress ? LONG_PRESS_MILLIS : delayMillis);
    }
    

    I set LONG_PRESS_MILLIS to 100 and it's working!

    Hope it helps!!! ;)

    0 讨论(0)
  • 2020-12-08 22:05

    I found this method. It is so simple.

    private static final long HOLD_PRESS_TIME = 1200; //ms unit    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_control);
    
        //Declare your button and add listener
        ImageView iconImg = findViewById(R.id.more_icon);
        iconImg.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.w("Button", "Button is pressed...");
                        //Start timer
                        timer.start();
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.w("Button", "Button is released...");
                        //Clear timer
                        timer.cancel();
                        break;
                }
                return false;
            }
        });
    
    
    }
    
    private CountDownTimer timer = new CountDownTimer(HOLD_PRESS_TIME, 200) {
        @Override
        public void onTick(long l) {
            Log.w("Button", "Count down..."+l); //It call onFinish() when l = 0
        }
    
        @Override
        public void onFinish() {
            //TODO your action here!
            Log.w("Button", "Count finish...");
    
        }
    };
    
    0 讨论(0)
  • 2020-12-08 22:10

    One method can do it. It's simple to use, easy to configure the time length, on time to trigger the callback.

    public static void setOnLongClickListener(final View view, final View.OnLongClickListener longClickListener, final long delayMillis)
    {
        view.setOnTouchListener(new View.OnTouchListener()
        {
            final Handler handler = new Handler();
            final Runnable runnable = new Runnable()
            {
                @Override
                public void run()
                {
                    longClickListener.onLongClick(view);
                    mRunning = false;
                }
            };
    
            boolean mRunning;
            boolean mOutside;
            RectF mRect = new RectF();
    
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                switch (event.getAction())
                {
                    case MotionEvent.ACTION_DOWN:
                    {
                        handler.postDelayed(runnable, delayMillis);
                        mRunning = true;
                        mOutside = false;
                        mRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
                        break;
                    }
                    case MotionEvent.ACTION_MOVE:
                        if (!mOutside)
                        {
                            mOutside = !mRect.contains(v.getLeft() + event.getX(), v.getTop() + event.getY());
                            if (mOutside)
                            {
                                handler.removeCallbacks(runnable);
                                mRunning = false;
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                    {
                        if (mRunning)
                            v.performClick();
                        handler.removeCallbacks(runnable);
                        mRunning = false;
                        break;
                    }
                    case MotionEvent.ACTION_CANCEL:
                    {
                        handler.removeCallbacks(runnable);
                        mRunning = false;
                        break;
                    }
                }
                return true; // !!!
            }
        });
    }
    
    0 讨论(0)
  • 2020-12-08 22:11

    This is the simplest way that I have found to achieve this behavior. It has a couple of advantages over the currently accepted answer.

    1. By checking view.isPressed we ensure that the onClick and onLongClick are not triggered if the touch event leaves the view. This mimics the default onClick and onLongClick behavior of the system.
    2. The event already stores timing information so there is no need to store the start time on ACTION_DOWN or calculate the current time on ACTION_UP ourselves. This means that we can use it for multiple views at the same time because we aren't tracking the event start time in a single variable outside of onTouch.

    NOTE: ViewConfiguration.getLongPressTimeout() is the default and you can change that check to use any value you want.

    NOTE: If the view is not normally clickable you will need to call view.setClickable(true) for the view.isPressed() check to work.

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (view.isPressed() && event.getAction() == MotionEvent.ACTION_UP) {
            long eventDuration = event.getEventTime() - event.getDownTime();
            if (eventDuration > ViewConfiguration.getLongPressTimeout()) {
                onLongClick(view);
            } else {
                onClick(view);
            }
        }
        return false;
    }
    

    If you, like @ampersandre, would like the long click event to trigger immediately once the delay period is reached instead of waiting for ACTION_UP then the following works well for me.

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            view.setTag(true);
        } else if (view.isPressed() && (boolean) view.getTag()) {
            long eventDuration = event.getEventTime() - event.getDownTime();
            if (eventDuration > ViewConfiguration.getLongPressTimeout()) {
                view.setTag(false);
                onLongClick(view);
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                onClick(view);
            }
        }
        return false;
    }
    
    0 讨论(0)
  • 2020-12-08 22:12

    I worked out a solution with the help of Rohan :)
    I adapted his answer to fit my requirements.

    When the user pushes the button, a thread is started. The thread sleeps for my desired delay, then when it wakes up, it executes whatever code I need it to do. When the user lets go, the thread is killed. This accomplishes what I want, because if the user lets go before the thread wakes up, the thread is interrupted and the action doesn't occur.

    I like this approach because it lets me execute my business logic as soon as the delay is up, which is good because I can give the user some feedback letting them know they've pushed long enough (the phone can vibrate, for example).
    The downside to this approach is: there is a risk that the user lets go of the button while your desired action is running, and kills the thread before everything is done. This isn't a huge problem for my case, because my business logic does very little; it just fires an event for some other class to process. If the action didn't complete fully, it's acceptable for the user to have to try again.

    The code is a little longer than I'd like, but if this is a common feature in your application, it's easily re-useable. Here's a code example:

    protected class MyLongClickListener implements View.OnTouchListener {
        private Thread longClickSensor;
    
        public boolean onTouch(View view, MotionEvent event) {
            // If the user is pressing down and there is no thread, make one and start it
            if (event.getAction() == MotionEvent.ACTION_DOWN && longClickSensor == null) {
                longClickSensor = new Thread(new MyDelayedAction());
                longClickSensor.start();
            }
            // If the user has let go and there was a thread, stop it and forget about the thread
            if (event.getAction() == MotionEvent.ACTION_UP && longClickSensor != null) {
                longClickSensor.interrupt();
                longClickSensor = null;
            }
            return false;
        }
    
        private class MyDelayedAction implements Runnable {
            private final long delayMs = 1200;
    
            public void run() {
                try {
                    Thread.sleep(delayMs); // Sleep for a while
                    doBusinessLogic();     // If the thread is still around after the sleep, do the work
                } catch (InterruptedException e) { return; }
            }
            private void doBusinessLogic() {
                // Make sure this logic is as quick as possible, or delegate it to some other class
                // through Broadcasted Intents, because if the user lets go while the work is happenening,
                // the thread will be interrupted.
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-08 22:16

    It is not possible to change the timer on the onLongClick event, it is managed by android itself.

    What is possible is to use .setOnTouchListener().

    Then register when the MotionEvent is a ACTION_DOWN.
    Note the current time in a variable.
    Then when a MotionEvent with ACTION_UP is registered and the current_time - actionDown time > 1200 ms then do something.

    so pretty much:

    Button button = new Button();
    long then = 0;
        button.setOnTouchListener(new OnTouchListener() {
    
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction() == MotionEvent.ACTION_DOWN){
                    then = (Long) System.currentTimeMillis();
                }
                else if(event.getAction() == MotionEvent.ACTION_UP){
                    if(((Long) System.currentTimeMillis() - then) > 1200){
                        return true;
                    }
                }
                return false;
            }
        })
    
    0 讨论(0)
提交回复
热议问题