Android - 按住按钮重复操作

68

我承认我是个新手,在尝试开发Android应用。我一直在网上寻找如何实现“按住按钮重复操作”的建议——我创建了一个由按钮组成的自定义数字键盘,并希望拥有类似于退格键的行为。已经做到这一步了,我叫来了一个朋友,他以前没有编写过Android应用,但是了解很多C# / Java知识,似乎知道自己在做什么。

下面的代码运行良好,但我觉得它可以更简洁地实现。如果我漏掉了什么,我道歉,但希望这能解释我的思路。我认为onTouchListener没问题,但线程处理的方式感觉不太对。

有更好或更简单的方法吗?

public class MyApp extends Activity {

private boolean deleteThreadRunning = false;
private boolean cancelDeleteThread = false;
private Handler handler = new Handler();
    
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    
    //May have missed some declarations here...
        
    Button_Del.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            
           switch (event.getAction())
           {
               case MotionEvent.ACTION_DOWN:
               {
                   handleDeleteDown();
                   return true;
               }
               
               case MotionEvent.ACTION_UP:
               {
                   handleDeleteUp();
                   return true;
               }
               
               default:
                   return false;
           }
        }

        private void handleDeleteDown() {

            if (!deleteThreadRunning)
                startDeleteThread();
        }

        private void startDeleteThread() {

            Thread r = new Thread() {
                
                @Override
                public void run() {
                    try {
                        
                        deleteThreadRunning = true;
                        while (!cancelDeleteThread) {
                            
                            handler.post(new Runnable() {   
                                @Override
                                public void run() {
                                    deleteOneChar();
                                }
                            });
                            
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(
                                    "Could not wait between char delete.", e);
                            }
                        }
                    }
                    finally
                    {
                        deleteThreadRunning = false;
                        cancelDeleteThread = false;
                    }
                }
            };
            
            // actually start the delete char thread
            r.start();
        }
    });
}

private void handleDeleteUp() {
    cancelDeleteThread = true;
}

private void deleteOneChar()
{
    String result = getNumberInput().getText().toString();
    int Length = result.length();
    
    if (Length > 0)
        getNumberInput().setText(result.substring(0, Length-1));
        //I've not pasted getNumberInput(), but it gets the string I wish to delete chars from
}

1
那似乎并不像一个问题。但是代码看起来还不错。 - Konstantin Burov
同意,问题是是否有更好的Android特定方法来完成它。感觉为了实现一些如此微不足道的事情而编写了很多代码。 - Mark
12个回答

103

这是一个更独立的实现方式,可与任何视图一起使用,并支持触摸事件:

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

}

使用方法:

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

8
我喜欢这个实现。然而,它有一个错误。onTouch函数应该返回true,至少在ACTION_DOWN情况下,否则将无法检测到其他触摸事件(在这种情况下,ACTION_UP将永远不会被调用)。 - bbedward
3
非常好的解决方案,非常感谢。在ACTION_DOWN中添加downView.setPressed(true);,在ACTION_CANCEL / ACTION_UP中添加downView.setPressed(false);,这将使侦听器响应选择器(如果有)。 - vir us
感谢您的评论,已添加 setPressed 调用。 - Oliv
1
为了进一步解耦,您可以在RepeatListener构造函数中使用(View view) -> view.performClick()传递方式来使用普通的View.setOnClickListener(或android:onClick)。额外的好处是每次重复发生时都会触发可访问性事件(和点击声音)。 - TWiStErRob
@Oliv 谢谢你的解决方案。我很感激你的帮助 :) - Harin Kaklotar
显示剩余6条评论

15

这里有一个名为AutoRepeatButton的简单类,它在许多情况下可以用作标准Button类的替代品:

package com.yourdomain.yourlibrary;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

public class AutoRepeatButton extends Button {

  private long initialRepeatDelay = 500;
  private long repeatIntervalInMilliseconds = 100;

  private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
    @Override
    public void run() {
      //Perform the present repetition of the click action provided by the user
      // in setOnClickListener().
      performClick();

      //Schedule the next repetitions of the click action, using a faster repeat
      // interval than the initial repeat delay interval.
      postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalInMilliseconds);
    }
  };

  private void commonConstructorCode() {
    this.setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction(); 
                if(action == MotionEvent.ACTION_DOWN) 
                {
                  //Just to be sure that we removed all callbacks, 
                  // which should have occurred in the ACTION_UP
                  removeCallbacks(repeatClickWhileButtonHeldRunnable);

                  //Perform the default click action.
                  performClick();

                  //Schedule the start of repetitions after a one half second delay.
                  postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
                }
                else if(action == MotionEvent.ACTION_UP) {
                  //Cancel any repetition in progress.
                  removeCallbacks(repeatClickWhileButtonHeldRunnable);
                }

                //Returning true here prevents performClick() from getting called 
                // in the usual manner, which would be redundant, given that we are 
                // already calling it above.
                return true;
      }
    });
  }

    public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        commonConstructorCode();
    }


    public AutoRepeatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        commonConstructorCode();
    }

  public AutoRepeatButton(Context context) {
    super(context);
    commonConstructorCode();
  }
}

我认为这是一个很好的解决方案。我将performClick()更改为performLongClick()并将performClick()移动到ACTION_UP条件中。我唯一的问题是我的按钮现在不再有动画效果了。 - SparkyNZ

8

Oliv's RepeatListenerClass很不错,但它无法处理"MotionEvent.ACTION_CANCEL",因此在该操作中处理程序无法删除回调。这会在PagerAdapter等方面产生问题,因此我添加了该事件情况。

private Rect rect; // Variable rect to hold the bounds of the view

public boolean onTouch(View view, MotionEvent motionEvent) {
    switch (motionEvent.getAction()) {
    case MotionEvent.ACTION_DOWN:
        handler.removeCallbacks(handlerRunnable);
        handler.postDelayed(handlerRunnable, initialInterval);
        downView = view;
        rect = new Rect(view.getLeft(), view.getTop(), view.getRight(),
                view.getBottom());
        clickListener.onClick(view);
        break;
    case MotionEvent.ACTION_UP:
        handler.removeCallbacks(handlerRunnable);
        downView = null;
        break;
    case MotionEvent.ACTION_MOVE:
        if (!rect.contains(view.getLeft() + (int) motionEvent.getX(),
                view.getTop() + (int) motionEvent.getY())) {
            // User moved outside bounds
            handler.removeCallbacks(handlerRunnable);
            downView = null;
            Log.d(TAG, "ACTION_MOVE...OUTSIDE");
        }
        break;
    case MotionEvent.ACTION_CANCEL:
        handler.removeCallbacks(handlerRunnable);
        downView = null;
        break;  
    }
    return false;
}

7
您的基本实现是正确的。但是我建议将该逻辑封装到另一个类中,以便您可以在其他地方使用它而不必重复编写代码。请参见例如“RepeatListener”类的此实现,它执行了与您想要执行的相同操作,只是针对滑动条。这里还有另一个线程提供的替代方案,但它与您的第一个解决方案非常相似。

谢谢I82Much,他们的代码看起来可以完成类似的工作。我会看看能否采纳它 :) - Mark
重复监听器是一个很好的选项。Oliv 的实现更加完整。 - Ben Wilkinson

5
这是基于Oliv的答案进行了以下修改后的答案:
  • 它调用视图上的performClickperformLongClick,而不是采取点击侦听器并直接调用onClick。这将触发标准的单击行为,例如长按时的触觉反馈。
  • 它可以配置为立即触发onClick(就像原始版本),或仅在ACTION_UP上触发,并且仅在没有单击事件触发的情况下触发(更像标准onClick的工作方式)。
  • 增加了一个无参构造函数,将immediateClick设置为false,并将系统标准的长按超时用于两个间隔。对我来说,如果存在的话,这最像是标准的“重复长按”。

这就是它:

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 either calls performClick once, or performLongClick repeatedly on an interval.
 * The performClick can be fired either immediately or on ACTION_UP if no clicks have
 * fired.  The performLongClick is fired once after initialInterval and then repeatedly
 * after 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.
 *
 * Based on https://dev59.com/x2855IYBdhLWcg3wik6D#12795551
 */
public class RepeatListener implements OnTouchListener {

    private Handler handler = new Handler();

    private final boolean immediateClick;
    private final int initialInterval;
    private final int normalInterval;
    private boolean haveClicked;

    private Runnable handlerRunnable = new Runnable() {
        @Override
        public void run() {
            haveClicked = true;
            handler.postDelayed(this, normalInterval);
            downView.performLongClick();
        }
    };

    private View downView;

    /**
     * @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
     * @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(
        boolean immediateClick,
        int initialInterval,
        int normalInterval)
    {
        if (initialInterval < 0 || normalInterval < 0)
            throw new IllegalArgumentException("negative interval");

        this.immediateClick = immediateClick;
        this.initialInterval = initialInterval;
        this.normalInterval = normalInterval;
    }

    /**
     * Constructs a repeat-listener with the system standard long press time
     * for both intervals, and no immediate click.
     */
    public RepeatListener()
    {
        immediateClick = false;
        initialInterval = android.view.ViewConfiguration.getLongPressTimeout();
        normalInterval = initialInterval;
    }

    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
        case MotionEvent.ACTION_DOWN:
            handler.removeCallbacks(handlerRunnable);
            handler.postDelayed(handlerRunnable, initialInterval);
            downView = view;
            if (immediateClick)
                downView.performClick();
            haveClicked = immediateClick;
            return true;
        case MotionEvent.ACTION_UP:
            // If we haven't clicked yet, click now
            if (!haveClicked)
                downView.performClick();
            // Fall through
        case MotionEvent.ACTION_CANCEL:
            handler.removeCallbacks(handlerRunnable);
            downView = null;
            return true;
        }

        return false;
    }

}

兄弟,这是完美的答案。因为它让我可以通过单击操作执行单击和通过重复操作执行长按。谢谢 +1。 - Harin Kaklotar

3

Carl的类是自包含的,运行良好。

我会使初始延迟和重复间隔可配置。为此,

需要修改attrs.xml文件。

<resources>
<declare-styleable name="AutoRepeatButton">
    <attr name="initial_delay"  format="integer" />
    <attr name="repeat_interval"  format="integer" />
</declare-styleable>
</resources>

AutoRepeatButton.java

    public AutoRepeatButton(Context context, AttributeSet attrs) {
    super(context, attrs);

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoRepeatButton);
    int n = a.getIndexCount();
    for (int i = 0; i < n; i++) {
        int attr = a.getIndex(i);

        switch (attr) {
        case R.styleable.AutoRepeatButton_initial_delay:
            initialRepeatDelay = a.getInt(attr, DEFAULT_INITIAL_DELAY);
            break;
        case R.styleable.AutoRepeatButton_repeat_interval:
            repeatIntervalInMilliseconds = a.getInt(attr, DEFAULT_REPEAT_INTERVAL);
            break;
        }
    }
    a.recycle();
    commonConstructorCode();
}

然后您可以像这样使用该类。
        <com.thepath.AutoRepeatButton
            xmlns:repeat="http://schemas.android.com/apk/res/com.thepath"
            android:id="@+id/btn_delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/selector_btn_delete"
            android:onClick="onBtnClick"
            android:layout_weight="1"
            android:layout_margin="2dp"

            repeat:initial_delay="1500"
            repeat:repeat_interval="150"
            />

3

Carl's class非常好,以下是一些修改可以加速(你按住时间越长,点击函数执行的速度就越快:

package com.yourdomain.yourlibrary;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

public class AutoRepeatButton extends Button {
    private long initialRepeatDelay = 500;
    private long repeatIntervalInMilliseconds = 100;

    // speedup
    private long repeatIntervalCurrent = repeatIntervalInMilliseconds;
    private long repeatIntervalStep = 2;
    private long repeatIntervalMin = 10;

    private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
        @Override
        public void run() {
            // Perform the present repetition of the click action provided by the user
            // in setOnClickListener().
            performClick();

            // Schedule the next repetitions of the click action, 
            // faster and faster until it reaches repeaterIntervalMin
            if (repeatIntervalCurrent > repeatIntervalMin)
                repeatIntervalCurrent = repeatIntervalCurrent - repeatIntervalStep;

            postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalCurrent);
        }
    };

    private void commonConstructorCode() {
        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                if (action == MotionEvent.ACTION_DOWN) {
                    // Just to be sure that we removed all callbacks,
                    // which should have occurred in the ACTION_UP
                    removeCallbacks(repeatClickWhileButtonHeldRunnable);

                    // Perform the default click action.
                    performClick();

                    // Schedule the start of repetitions after a one half second delay.
                    repeatIntervalCurrent = repeatIntervalInMilliseconds;
                    postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
                } else if (action == MotionEvent.ACTION_UP) {
                    // Cancel any repetition in progress.
                    removeCallbacks(repeatClickWhileButtonHeldRunnable);
                }

                // Returning true here prevents performClick() from getting called
                // in the usual manner, which would be redundant, given that we are
                // already calling it above.
                return true;
            }
        });
    }

    public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        commonConstructorCode();
    }

     public AutoRepeatButton(Context context, AttributeSet attrs) {
     super(context, attrs);
     commonConstructorCode();
     }

    public AutoRepeatButton(Context context) {
        super(context);
        commonConstructorCode();
    }
}

1
如果这适用于所有视图而不仅仅是按钮,那就更好了。 - harveyslash

1
这是一个 Kotlin 版本,它具有以下特点:
  1. combines the answers by benkc and nikib3ro (to speed-up the action on a continuous long click);

  2. prevents the runnable to run indefinitely if the view (e.g. button) is disabled during the process.

    import android.os.Handler
    import android.os.Looper
    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 either calls performClick once, or performLongClick repeatedly on an interval.
     * The performClick can be fired either immediately or on ACTION_UP if no clicks have
     * fired.  The performLongClick is fired once after repeatInterval.
     *
     * Continuous LongClick can be speed-up by setting intervalAcceleration
     *
     * Interval is scheduled after the onClick completes, so it has to run fast.
     * If it runs slow, it does not generate skipped onClicks.
     *
     * Based on:
     * https://dev59.com/x2855IYBdhLWcg3wik6D#31331293
     * https://dev59.com/x2855IYBdhLWcg3wik6D#21644404
     *
     * @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
     * @param interval The interval after first click event
     * @param intervalAcceleration The amount of time reduced from interval to speed-up the action
     *
     */
    class RepeatListener(private val immediateClick: Boolean, private val interval: Int, private val intervalAcceleration: Int) : OnTouchListener {
    
        private var activeView: View? = null
        private val handler = Handler(Looper.getMainLooper())
        private var handlerRunnable: Runnable
        private var haveClicked = false
    
        private var repeatInterval: Int = interval
        private val repeatIntervalMin: Int = 100
    
        init {
            handlerRunnable = object : Runnable {
                override fun run() {
    
                    if(activeView!!.isEnabled) {
                        haveClicked = true
    
                        // Schedule the next repetitions of the click action,
                        // faster and faster until it reaches repeaterIntervalMin
                        if (repeatInterval > repeatIntervalMin) {
                            repeatInterval -= intervalAcceleration
                        }
    
                        handler.postDelayed(this, repeatInterval.toLong())
                        activeView!!.performLongClick()
    
                    }else{
                        clearHandler() //stop the loop if the view is disabled during the process
                    }
                }
            }
        }
    
        override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
            when (motionEvent.action) {
                MotionEvent.ACTION_DOWN -> {
                    handler.removeCallbacks(handlerRunnable)
                    handler.postDelayed(handlerRunnable, repeatInterval.toLong())
                    activeView = view
                    if (immediateClick) activeView!!.performClick()
                    haveClicked = immediateClick
                    return true
                }
                MotionEvent.ACTION_UP -> {
                    // If we haven't clicked yet, click now
                    if (!haveClicked) activeView!!.performClick()
                    clearHandler()
                    return true
                }
                MotionEvent.ACTION_CANCEL -> {
                    clearHandler()
                    return true
                }
            }
            return false
        }
    
        private fun clearHandler(){
            handler.removeCallbacks(handlerRunnable)
            activeView = null
    
            //reset the interval to avoid starting with the sped up interval
            repeatInterval = interval
        }
    }
    

1

Olivsephiron的答案都很好,但我想要使用重复动作与常规View.OnClickListener一起,并处理移动操作。

View view = //take somewhere    
view.setOnClickListener(v -> { /*do domething*/ });
view.setOnTouchListener(new RepeatListener(/*almost the same as in Oliv's answer*/); 

//if you want to use touch listener without click listener,  
//make sure that view has setClickable(true)

So it can be done like this without conflicts:

import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class RepeatListener implements OnTouchListener {

    private final static int TOUCH_OFFSET = 20;

    private final Handler handler = new Handler(Looper.getMainLooper());

    private final int initialInterval;
    private final int normalInterval;
    private final Runnable startListener;
    private final Runnable actionListener;

    private final Rect touchHoldRect = new Rect();

    private View touchedView;
    private boolean calledAtLeastOnce = false;

    private final Runnable handlerRunnable = new Runnable() {
        @Override
        public void run() {
            if (touchedView.isEnabled()) {
                handler.postDelayed(this, normalInterval);
                actionListener.run();
                if (!calledAtLeastOnce && startListener != null) {
                    startListener.run();
                }
                calledAtLeastOnce = true;
            } else {
                handler.removeCallbacks(handlerRunnable);
                touchedView.setPressed(false);
                touchedView = null;
                calledAtLeastOnce = false;
            }
        }
    };

    public RepeatListener(int initialInterval,
                          int normalInterval,
                          Runnable startListener,
                          Runnable actionListener) {
        if (actionListener == null) {
            throw new IllegalArgumentException("null runnable");
        }
        if (initialInterval < 0 || normalInterval < 0) {
            throw new IllegalArgumentException("negative interval");
        }

        this.initialInterval = initialInterval;
        this.normalInterval = normalInterval;
        this.startListener = startListener;
        this.actionListener = actionListener;
    }

    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                handler.removeCallbacks(handlerRunnable);
                calledAtLeastOnce = false;
                handler.postDelayed(handlerRunnable, initialInterval);
                touchedView = view;
                touchHoldRect.set(view.getLeft() - TOUCH_OFFSET,
                        view.getTop() - TOUCH_OFFSET,
                        view.getRight() + TOUCH_OFFSET,
                        view.getBottom() + TOUCH_OFFSET);
                return false;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                handler.removeCallbacks(handlerRunnable);
                if (calledAtLeastOnce) {
                    touchedView.setPressed(false);
                }
                touchedView = null;
                boolean processed = calledAtLeastOnce;

                calledAtLeastOnce = false;
                return processed;
            }
            case MotionEvent.ACTION_MOVE: {
                if (!touchHoldRect.contains(
                        view.getLeft() + (int) motionEvent.getX(),
                        view.getTop() + (int) motionEvent.getY())) {
                    handler.removeCallbacks(handlerRunnable);
                }
                break;
            }
        }
        return false;
    }
}

1

它可以工作,但我发现需要一些小调整。 我添加了一个计数器。 如果计数器达到5,间隔时间加快。 如果计数器达到15,速度更快。

--

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.LinearLayout;
import android.view.View;

import android.os.Handler;
import android.view.MotionEvent;
import android.widget.Toast;

public class MainActivity extends Activity {
int tt=0;

int interval =500;//how fast to repeat
int interval_base=500;
int repeat=0;//counted to switch speeds

Context context;

LinearLayout linearLayout;

TextView Info;

@Override
protected void onCreate(Bundle savedInstanceState) {
    
super.onCreate(savedInstanceState);
    
setContentView(R.layout.activity_main);
context = this;
linearLayout = findViewById(R.id.rootLayout);

Info = new TextView(this);
Info.setText("0");

TextView textView = new TextView(this);
    textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    
textView.setGravity(Gravity.CENTER);
    textView.setText("Click me");


textView.setOnTouchListener(new LongTouchIntervalListener(interval) {
    @Override
    public void onTouchInterval() {
        // do whatever you want
        repeat++;
        tt++;
        if(repeat==5){
            interval =250;
            Toast("Fast");
        }
        if(repeat==15){
            interval =125;
            Toast("Faster");
        }

        Info.setText(tt+"");
    }
 }); 

linearLayout.addView(textView);
linearLayout.addView(Info);
}


//Standard Toast message
public void Toast(String msg){ 
    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); 
}

//create on touch long repeating 
public abstract class LongTouchIntervalListener implements View.OnTouchListener {

private final long touchIntervalMills;
private long touchTime;
private Handler handler = new Handler();

public LongTouchIntervalListener(final long touchIntervalMills) {
    if (touchIntervalMills <= 0) {
        throw new IllegalArgumentException("Touch interval must be more than zero");
    }
    this.touchIntervalMills = touchIntervalMills;
}

public abstract void onTouchInterval();

@Override
public boolean onTouch(final View v, final MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            onTouchInterval();
            touchTime = System.currentTimeMillis();

handler.postDelayed(touchInterval, 
touchIntervalMills);

            return true;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
interval=interval_base;//reset to base
repeat=0;//reset counter
touchTime = 0;
handler.removeCallbacks(touchInterval);
return true;
        default:
            break;
    }
    return false;
}

private final Runnable touchInterval = new Runnable() {
    @Override
    public void run() {
        onTouchInterval();
        if (touchTime > 0) {
handler.postDelayed(this, interval);
        }
    }
};
}

}

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