android重复按住按钮的动作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10511423/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 04:00:39  来源:igfitidea点击:

android repeat action on pressing and holding a button

android

提问by MH.

I want to implement repeat action on pressing and holding a button. Example: When user click on a button and hold it,it should call a similar method again and again on a fixed interval until the user remove his finger from the button.

我想在按住按钮时实现重复操作。示例:当用户点击一个按钮并按住它时,它应该以固定的时间间隔一次又一次地调用类似的方法,直到用户将手指从按钮上移开。

回答by MH.

There are multiple ways to accomplish this, but a pretty straightforward one would be to post a Runnableon a Handlerwith a certain delay. In it's most basic form, it will look somewhat like this:

有多种方法可以实现这一点,但一个非常简单的方法是RunnableHandler有一定延迟的情况下在 a上发布。在最基本的形式中,它看起来有点像这样:

Button button = (Button) findViewById(R.id.button);
button.setOnTouchListener(new View.OnTouchListener() {

    private Handler mHandler;

    @Override public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if (mHandler != null) return true;
            mHandler = new Handler();
            mHandler.postDelayed(mAction, 500);
            break;
        case MotionEvent.ACTION_UP:
            if (mHandler == null) return true;
            mHandler.removeCallbacks(mAction);
            mHandler = null;
            break;
        }
        return false;
    }

    Runnable mAction = new Runnable() {
        @Override public void run() {
            System.out.println("Performing action...");
            mHandler.postDelayed(this, 500);
        }
    };

});

The idea is pretty simple: post a Runnablecontaining the repeated action on a Handlerwhen the 'down' touch action occurs. After that, don't post the Runnableagain until the 'up' touch action has passed. The Runnablewill keep posting itself to the Handler(while the 'down' touch action is still happening), until it gets removed by the touch up action - that's what enables the 'repeating' aspect.

这个想法非常简单:当“向下”触摸动作发生时,Runnable在 a 上发布包含重复动作的 a Handler。之后,Runnable在“向上”触摸动作通过之前不要再次发布。该Runnable会继续发布自己的Handler(而“向下”的触摸动作仍在进行),直到它被通过触摸后续行动删除-这就是使“重复”的方面。

Depending on the actual behaviour of the button and its onclick/ontouch you're after, you might want to do the initial post without a delay.

根据按钮的实际行为及其您所追求的 onclick/ontouch,您可能希望立即进行初始发布。

回答by Cícero Moura

Here is another approach in case you use the normal view click. If so, when you release the view, the click listener is called. So I take advantage of the long click listener to do the first part.

如果您使用普通视图单击,这是另一种方法。如果是这样,当您释放视图时,将调用单击侦听器。所以我利用长点击监听器来做第一部分。

button.setOnLongClickListener(new OnLongClickListener() {

            private Handler mHandler;

            @Override
            public boolean onLongClick(View view) {
                final Runnable mAction = new Runnable() {
                    @Override
                    public void run() {
                        //do something here 
                        mHandler.postDelayed(this, 1000);
                    }
                };

                mHandler = new Handler();
                mHandler.postDelayed(mAction, 0);

                button.setOnTouchListener(new View.OnTouchListener() {

                    @SuppressLint("ClickableViewAccessibility")
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        switch (event.getAction()) {
                            case MotionEvent.ACTION_CANCEL:
                            case MotionEvent.ACTION_MOVE:
                            case MotionEvent.ACTION_UP:
                                if (mHandler == null) return true;
                                mHandler.removeCallbacks(mAction);
                                mHandler = null;
                                button.setOnTouchListener(null);
                                return false;
                        }
                        return false;
                    }

                });


                return true;
            }
        });

回答by cstrutton

Although not a great idea. It could be accomplished by starting a timer on onKeyDownto fire at an interval during which you move the cursor one step and restart the timer. You could then cancel the timer on the onKeyUpevent. The way this works on other systems is to typically to move on the first key down then wait a bit to ensure that the user is definitly holding the button... then the repeat can be a bit faster. Think of a keyboard auto repeating. This should work and should not affect the ui thread adversely.

虽然不是一个好主意。这可以通过启动一个计时器来完成,onKeyDown在您将光标移动一步并重新启动计时器的时间间隔内触发。然后,您可以取消onKeyUp事件的计时器。这在其他系统上的工作方式通常是向下移动第一个键,然后稍等片刻以确保用户明确按住按钮......然后重复可能会更快一些。想想键盘自动重复。这应该可以工作,并且不会对 ui 线程产生不利影响。

回答by Kenneth Argo

A compatible Kotlin version and example based on Faisal Shaikhanswer:

基于Faisal Shaikh答案的兼容 Kotlin 版本和示例:

package com.kenargo.compound_widgets

import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener

/**
 * A class, that can be used as a TouchListener on any view (e.g. a Button).
 * It cyclically runs a clickListener, emulating keyboard-like behaviour. First
 * click is fired immediately, next one after the initialInterval, and subsequent
 * ones after the initialRepeatDelay.
 *
 * @param initialInterval The interval after first click event
 * @param initialRepeatDelay The interval after second and subsequent click events
 *
 * @param clickListener The OnClickListener, that will be called
 * periodically
 *
 * Interval is scheduled after the onClick completes, so it has to run fast.
 * If it runs slow, it does not generate skipped onClicks. Can be rewritten to
 * achieve this.
 *
 * Usage:
 *
 * someView.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() {
 *  @Override
 *  public void onClick(View view) {
 *      // the code to execute repeatedly
 *  }
 * }));
 *
 * Kotlin example:
 *  someView.setOnTouchListener(RepeatListener(defaultInitialTouchTime, defaultRepeatDelayTime, OnClickListener {
 *      // the code to execute repeatedly
 *  }))
 *
 */
class RepeatListener(
    initialInterval: Int,
    initialRepeatDelay: Int,
    clickListener: View.OnClickListener
) : OnTouchListener {

    private val handler = Handler()

    private var initialInterval: Int
    private var initialRepeatDelay: Int

    private var clickListener: View.OnClickListener
    private var touchedView: View? = null

    init {
        require(!(initialInterval < 0 || initialRepeatDelay < 0)) { "negative intervals not allowed" }

        this.initialInterval = initialRepeatDelay
        this.initialRepeatDelay = initialInterval

        this.clickListener = clickListener
    }

    private val handlerRunnable: Runnable = run {
        Runnable {
            if (touchedView!!.isEnabled) {

                handler.postDelayed(handlerRunnable, initialRepeatDelay.toLong())
                clickListener.onClick(touchedView)
            } else {

                // if the view was disabled by the clickListener, remove the callback
                handler.removeCallbacks(handlerRunnable)
                touchedView!!.isPressed = false
                touchedView = null
            }
        }
    }

    override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {

        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                handler.removeCallbacks(handlerRunnable)
                handler.postDelayed(handlerRunnable, initialRepeatDelay.toLong())
                touchedView = view
                touchedView!!.isPressed = true
                clickListener.onClick(view)
                return true
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                handler.removeCallbacks(handlerRunnable)
                touchedView!!.isPressed = false
                touchedView = null
                return true
            }
        }

        return false
    }
}

回答by Faisal Shaikh

This is more independent implementation, usable with any View, that supports touch event.

这是更独立的实现,可用于任何支持触摸事件的视图。

import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;

/**
 * A class, that can be used as a TouchListener on any view (e.g. a Button).
 * It cyclically runs a clickListener, emulating keyboard-like behaviour. First
 * click is fired immediately, next one after the initialInterval, and subsequent
 * ones after the normalInterval.
 *
 * <p>Interval is scheduled after the onClick completes, so it has to run fast.
 * If it runs slow, it does not generate skipped onClicks. Can be rewritten to
 * achieve this.
 */
public class RepeatListener implements OnTouchListener {

    private Handler handler = new Handler();

    private int initialInterval;
    private final int normalInterval;
    private final OnClickListener clickListener;
    private View touchedView;

    private Runnable handlerRunnable = new Runnable() {
        @Override
        public void run() {
            if(touchedView.isEnabled()) {
                handler.postDelayed(this, normalInterval);
                clickListener.onClick(touchedView);
            } else {
                // if the view was disabled by the clickListener, remove the callback
                handler.removeCallbacks(handlerRunnable);
                touchedView.setPressed(false);
                touchedView = null;
            }
        }
    };

    /**
     * @param initialInterval The interval after first click event
     * @param normalInterval The interval after second and subsequent click 
     *       events
     * @param clickListener The OnClickListener, that will be called
     *       periodically
     */
    public RepeatListener(int initialInterval, int normalInterval, 
            OnClickListener clickListener) {
        if (clickListener == null)
            throw new IllegalArgumentException("null runnable");
        if (initialInterval < 0 || normalInterval < 0)
            throw new IllegalArgumentException("negative interval");

        this.initialInterval = initialInterval;
        this.normalInterval = normalInterval;
        this.clickListener = clickListener;
    }

    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
        case MotionEvent.ACTION_DOWN:
            handler.removeCallbacks(handlerRunnable);
            handler.postDelayed(handlerRunnable, initialInterval);
            touchedView = view;
            touchedView.setPressed(true);
            clickListener.onClick(view);
            return true;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handler.removeCallbacks(handlerRunnable);
            touchedView.setPressed(false);
            touchedView = null;
            return true;
        }

        return false;
    }

}

Usage:

用法:

Button button = new Button(context);
button.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() {
  @Override
  public void onClick(View view) {
    // the code to execute repeatedly
  }
}));

Original Answer

原答案

回答by avimak

You can register a View.OnKeyListenerfor that View. Please take into an account that it is better to debounce such callbacks, otherwise - in case your method doing something even slightly "heavy" - the UI won't be smooth.

您可以View.OnKeyListener为该视图注册一个。请考虑到最好对此类回调进行去抖动,否则 - 如果您的方法做了一些稍微“重”的事情 - 用户界面将不会流畅。