2010-11-26 124 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; 
    } 

}