Android - 按住按钮重复操作

Mar*_*ark 53 android handler ontouchlistener

大家早,

我会直接承认,我是开发新手并尝试使用Android.我一直在尝试搜索'网络以找到关于如何实现一些"按钮重复操作"的建议 - 我已经从按钮创建了一个自定义小键盘,并希望有类似退格的行为.到目前为止,我曾经拜访过一位没有编过Android的朋友,但他做了很多C#/ Java,似乎知道他在做什么.

下面的代码工作正常,但我觉得它可以更整齐地完成.如果我错过了比特,我很抱歉,但希望这能解释我的做法.我认为onTouchListener没问题,但处理Threads的方式并不合适.

有没有更好或更简单的方法来做到这一点?

谢谢,

中号

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
}
Run Code Online (Sandbox Code Playgroud)

Oli*_*liv 83

这是一个更独立的实现,可用于支持触摸事件的任何View

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

}
Run Code Online (Sandbox Code Playgroud)

用法:

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

  • 我喜欢这个实现.但是它有一个错误.onTouch functin应该返回true,至少在ACTION_DOWN情况下,否则将不会检测到其他触摸事件(在这种情况下将永远不会调用ACTION_UP) (8认同)
  • 很好的解决方案,非常感谢.在ACTION_DOWN中添加`downView.setPressed(true);`和在ACTION_CANCEL/ACTION_UP部分中添加`downView.setPressed(false);`将使侦听器响应选择器(如果有的话). (3认同)

Car*_*arl 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();
  }
}
Run Code Online (Sandbox Code Playgroud)


I82*_*uch 7

你的基本实现是健全的.但是,我会将该逻辑封装到另一个类中,以便您可以在其他地方使用它而无需复制代码.请参阅例如"RepeatListener"类的实现,执行您想要执行的相同操作,但搜索栏除外.

这是另一个带有替代解决方案的线程,但它与您的第一个解决方案非常相似.


小智 7

Oliv的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;
}
Run Code Online (Sandbox Code Playgroud)


ben*_*nkc 5

这是基于 Oliv 的答案,并进行了以下调整:

  • 它不是采用单击侦听器并onClick直接调用,而是在视图上调用performClickperformLongClick。这将触发标准的点击行为,如长按时的触觉反馈。
  • 它可以配置为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 http://stackoverflow.com/a/12795551/642160
 */
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;
    }

}
Run Code Online (Sandbox Code Playgroud)