2010-11-26 98 views
44

早上全部,Android - 按住重复按钮

我会承认,我是新来的开发人员,并试着在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 
} 
+0

并没有真正像一个问题。该代码看起来o.k.虽然。 – 2010-11-26 11:23:51

+0

同意,问题是是否有更好的,Android的具体方式来做到这一点。感觉像很多代码来获得如此微不足道的东西。 – Mark 2010-11-26 12:43:52

回答

69

这是更加独立的实现,可用于任何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 Runnable handlerRunnable = new Runnable() { 
     @Override 
     public void run() { 
      handler.postDelayed(this, normalInterval); 
      clickListener.onClick(downView); 
     } 
    }; 

    private View downView; 

    /** 
    * @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); 
      downView = view; 
      downView.setPressed(true); 
      clickListener.onClick(view); 
      return true; 
     case MotionEvent.ACTION_UP: 
     case MotionEvent.ACTION_CANCEL: 
      handler.removeCallbacks(handlerRunnable); 
      downView.setPressed(false); 
      downView = 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 
    } 
})); 
+7

我喜欢这个实现。但是它有一个错误。至少在ACTION_DOWN情况下,onTouch functin应返回true,否则不会检测到其他触摸事件(在此情况下ACTION_UP将永远不会被调用) – bbedward 2013-02-01 17:18:07

7

你的基本实现是健全的。但是,我会将该逻辑封装到另一个类中,以便您可以在其他位置使用它,而无需重复代码。见例如this执行“RepeatListener”类,除了寻找栏之外,它们可以执行相同的操作。

这是another thread with an alternative solution,但它与您的第一个非常相似。

+0

感谢I82Much,他们的代码看起来可以做类似的工作。我会看看我是否可以把它放在船上:) – Mark 2010-11-26 17:09:02

+0

重复监听器是一个不错的选择。 Oliv的实施更加完整。 – 2013-01-23 22:08:07

14

下面是一个叫做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(); 
    } 
} 
+0

我认为这是一个很好的解决方案。我改变了pe​​rformClick()来执行LongClick()并将performClick()移入ACTION_UP条件。我唯一的问题是我的按钮现在不生动画。 – SparkyNZ 2014-10-03 22:31:17

4

卡尔的类是自包含的,工作正常。

我会使初始延迟和重复间隔可配置。 要做到这一点,

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" 
      /> 
2

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

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

下面是基于杜仲在以下方面调整答案:

  • 而不是采取一个点击监听器,并直接调用onClick,它调用performClick或在视图上performLongClick。这将触发标准点击行为,如长时间点击触觉反馈。
  • 它可以配置为立即触发onClick(如原始),或仅在ACTION_UP上触发,并且仅在未触发任何点击事件(更像标准onClick工作方式)时触发onClick
  • immediateClick设置为false并使用系统标准long press timeout代替这两个间隔的替代无参数构造函数。对我而言,这感觉最像是一个标准的“重复长按”,如果它存在的话。

这就是:

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

}