web-dev-qa-db-ja.com

Android-ボタンを押し続けるとアクションを繰り返す

みなさん、おはよう、

私は、開発の初心者であり、Androidで手を試していることをすぐに認めます。私は「ネットを検索して、「アクションを繰り返すためのボタンを保持」を実装する方法に関するアドバイスを見つけようとしています。ボタンからカスタムテンキーを作成し、バックスペースのような動作が必要です。これまでのところ、私は以前にAndroidをコーディングしていなかったが、多くのC#/ Javaを行い、彼が何をしているか知っているようだ。

以下のコードは問題なく動作しますが、もっときれいにできると思います。ビットを逃したことをおaびしますが、うまくいけばこれが私のアプローチを説明します。 onTouchListenerは大丈夫だと思いますが、スレッドの処理方法は正しくありません。

これを行うためのより良いまたはより簡単な方法はありますか?

おかげで、

M

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
}
50
Mark

これはより独立した実装であり、任意のビューで使用でき、タッチイベントをサポートします。

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
  }
}));
81
Oliv

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

基本的な実装は適切です。ただし、そのロジックを別のクラスにカプセル化して、コードを複製せずに他の場所で使用できるようにします。例参照 this シークバーを除いて、やりたいことと同じことを行う「RepeatListener」クラスの実装。

代替ソリューションを備えた別のスレッド ですが、これは最初のスレッドと非常によく似ています。

7
I82Much

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

以下は、Olivに基づいて次の調整を行った回答です。

  • クリックリスナーを使用してonClickを直接呼び出す代わりに、ビューでperformClickまたはperformLongClickを呼び出します。これにより、ロングクリックの触覚フィードバックのような標準のクリック動作がトリガーされます。
  • 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;
    }

}
4
benkc

カールのクラスは自己完結型であり、正常に動作します。

初期遅延と繰り返し間隔を構成可能にします。そうするには、

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"
            />
4
JonA

Carlのクラス はかなり良いです。ここでは、高速化を可能にする変更があります(より長くクリックすると、クリック機能が実行されます:

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

ネストされたクリックリスナーを使用しない、少し異なるソリューションを次に示します。

使用法:

 view.setOnTouchListener(new LongTouchIntervalListener(1000) {
        @Override
        public void onTouchInterval() {
            // do whatever you want
        }
 });

そして、リスナー自体:

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 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:
                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, touchIntervalMills);
            }
        }
    };
}
0
Leo Droidcoder

カールの授業は私に適しています。しかし、ボタンを押してドラッグすると問題になります。ボタン領域から出た場合でも、クリックイベントが発生します。

' Android:ユーザーがタッチしてボタン領域からドラッグしたかどうかを検出しますか? 'のようなACTION_MOVEに関するコードを追加してください

0
PeanutStars