Android-按钮重复动作
-
28-09-2019 - |
题
大家早,
我会直接承认我是新手开发,并尝试了Android。我一直在尝试搜索'网络,以查找有关如何实现一些“保持按钮重复操作”的建议 - 我已经从按钮创建了一个自定义的numpad,并且想要类似于backspace的行为。到目前为止,我打电话给一个以前从未编码过Android的朋友,但做了很多C# / Java,似乎知道他在做什么。
下面的代码工作正常,但是我觉得可以更整齐地完成。我深表歉意,如果我错过了Bits,但希望这可以解释我的方法。我认为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
}
解决方案
这是支持触摸事件的任何视图的更独立的实现:
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
}
}));
其他提示
这是一个名为AutorePeatButton的简单类,在许多情况下,可以用作标准按钮类的倒入替换:
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();
}
}
您的基本实现是合理的。但是,我将该逻辑封装到另一个类中,以便您可以在其他地方使用它而无需重复代码。参见例如 这 实施“重复列表”类,除了寻求栏外,还可以做同样的事情。
这是 带有替代解决方案的另一个线程, ,但它与您的第一个非常相似。
奥利夫的重复列赛卷 很好,但是它不能处理“ 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;
}
卡尔的班级是独立的,效果很好。
我将进行初始延迟,并重复间隔可配置。为此,
attr.xml
<resources>
<declare-styleable name="AutoRepeatButton">
<attr name="initial_delay" format="integer" />
<attr name="repeat_interval" format="integer" />
</declare-styleable>
</resources>
自动驾驶
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"
/>
这是基于Oliv的答案,并进行以下调整:
- 而不是单击侦听器并打电话
onClick
直接调用performClick
或者performLongClick
在视图上。这将触发标准点击行为,例如长时间单击时的触觉反馈。 - 它可以配置为发射
onClick
立即(像原始),或仅在ACTION_UP
而且只有没有点击事件发射(更像是如何标准的onClick
作品)。 - 设置的备用no-arg构造函数
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;
}
}
卡尔的课 很好,这里是允许加速的修改(您保留的时间越长,可以执行更快的点击函数:
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();
}
}
卡尔的课对我有好处。但是,当按下按钮和拖动是某个问题。如果离开按钮区域,请继续进行点击事件。
请添加有关action_move的代码 Android:检测用户是否触摸和拖出按钮区域?'
这是一些不同的解决方案,而无需使用嵌套的单击侦听器。
用法:
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);
}
}
};
}