早上好,
我直接承认我是开发新手并尝试使用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
}
答案 0 :(得分:80)
这是一个更独立的实现,可用于支持触摸事件的任何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;
}
}
用法:
Button button = new Button(context);
button.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() {
@Override
public void onClick(View view) {
// the code to execute repeatedly
}
}));
答案 1 :(得分: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();
}
}
答案 2 :(得分:7)
您的基本实施是合理的。但是,我会将该逻辑封装到另一个类中,以便您可以在其他地方使用它而无需复制代码。参见例如this实现“RepeatListener”类,它执行您想要执行的相同操作,但搜索栏除外。
这是another thread with an alternative solution,但它与你的第一个非常相似。
答案 3 :(得分:7)
Oliv's RepeatListenerClass非常好,但它没有处理&#34; MotionEvent.ACTION_CANCEL&#34;,因此处理程序不会删除该操作中的回调。这会在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;
}
答案 4 :(得分: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"
/>
答案 5 :(得分:4)
以下是基于Oliv的答案,并进行了以下调整:
onClick
,它会在视图上调用performClick
或performLongClick
。这将触发标准点击行为,例如长按一下的触觉反馈。onClick
(与原始版本一样),或仅在ACTION_UP
上触发,并且仅在未触发任何点击事件时触发(更像是标准onClick
如何工作)。immediateClick
设置为false,并对两个间隔使用系统标准long press timeout。对我来说,这感觉最像是一个标准&#34;重复长按&#34;如果它存在的话就会存在。这是:
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;
}
}
答案 6 :(得分: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 :(得分:0)
请添加关于ACTION_MOVE的代码,例如“Android: Detect if user touches and drags out of button region?”
答案 8 :(得分:0)
这里有一个略有不同的解决方案,没有使用嵌套的点击侦听器。
用法:
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);
}
}
};
}
答案 9 :(得分:0)
Oliv's和sephiron's的答案很好,但我想将重复动作与常规View.OnClickListener一起使用,并进行动作处理。
View view = //take somewhere
view.setOnClickListener(v -> { /*do domething*/ });
view.setOnTouchListener(new RepeatListener(/*almost the same as in Oliv's answer*/);
//if you want to use touch listener without click listener,
//make sure that view has setClickable(true)
这样可以做到没有冲突:
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class RepeatListener implements OnTouchListener {
private final static int TOUCH_OFFSET = 20;
private final Handler handler = new Handler(Looper.getMainLooper());
private final int initialInterval;
private final int normalInterval;
private final Runnable startListener;
private final Runnable actionListener;
private final Rect touchHoldRect = new Rect();
private View touchedView;
private boolean calledAtLeastOnce = false;
private final Runnable handlerRunnable = new Runnable() {
@Override
public void run() {
if (touchedView.isEnabled()) {
handler.postDelayed(this, normalInterval);
actionListener.run();
if (!calledAtLeastOnce && startListener != null) {
startListener.run();
}
calledAtLeastOnce = true;
} else {
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
calledAtLeastOnce = false;
}
}
};
public RepeatListener(int initialInterval,
int normalInterval,
Runnable startListener,
Runnable actionListener) {
if (actionListener == null) {
throw new IllegalArgumentException("null runnable");
}
if (initialInterval < 0 || normalInterval < 0) {
throw new IllegalArgumentException("negative interval");
}
this.initialInterval = initialInterval;
this.normalInterval = normalInterval;
this.startListener = startListener;
this.actionListener = actionListener;
}
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN: {
handler.removeCallbacks(handlerRunnable);
calledAtLeastOnce = false;
handler.postDelayed(handlerRunnable, initialInterval);
touchedView = view;
touchHoldRect.set(view.getLeft() - TOUCH_OFFSET,
view.getTop() - TOUCH_OFFSET,
view.getRight() + TOUCH_OFFSET,
view.getBottom() + TOUCH_OFFSET);
return false;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
handler.removeCallbacks(handlerRunnable);
if (calledAtLeastOnce) {
touchedView.setPressed(false);
}
touchedView = null;
boolean processed = calledAtLeastOnce;
calledAtLeastOnce = false;
return processed;
}
case MotionEvent.ACTION_MOVE: {
if (!touchHoldRect.contains(
view.getLeft() + (int) motionEvent.getX(),
view.getTop() + (int) motionEvent.getY())) {
handler.removeCallbacks(handlerRunnable);
}
break;
}
}
return false;
}
}
答案 10 :(得分:0)
这是一个 Kotlin 版本:
如果在此过程中禁用了视图(例如按钮),则阻止可运行对象无限期运行。
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.View
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 repeatInterval.
*
* Continuous LongClick can be speed-up by setting intervalAcceleration
*
* 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:
* https://stackoverflow.com/a/31331293/8945452
* https://stackoverflow.com/a/21644404/8945452
*
* @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
* @param interval The interval after first click event
* @param intervalAcceleration The amount of time reduced from interval to speed-up the action
*
*/
class RepeatListener(private val immediateClick: Boolean, private val interval: Int, private val intervalAcceleration: Int) : OnTouchListener {
private var activeView: View? = null
private val handler = Handler(Looper.getMainLooper())
private var handlerRunnable: Runnable
private var haveClicked = false
private var repeatInterval: Int = interval
private val repeatIntervalMin: Int = 100
init {
handlerRunnable = object : Runnable {
override fun run() {
if(activeView!!.isEnabled) {
haveClicked = true
// Schedule the next repetitions of the click action,
// faster and faster until it reaches repeaterIntervalMin
if (repeatInterval > repeatIntervalMin) {
repeatInterval -= intervalAcceleration
}
handler.postDelayed(this, repeatInterval.toLong())
activeView!!.performLongClick()
}else{
clearHandler() //stop the loop if the view is disabled during the process
}
}
}
}
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
handler.removeCallbacks(handlerRunnable)
handler.postDelayed(handlerRunnable, repeatInterval.toLong())
activeView = view
if (immediateClick) activeView!!.performClick()
haveClicked = immediateClick
return true
}
MotionEvent.ACTION_UP -> {
// If we haven't clicked yet, click now
if (!haveClicked) activeView!!.performClick()
clearHandler()
return true
}
MotionEvent.ACTION_CANCEL -> {
clearHandler()
return true
}
}
return false
}
private fun clearHandler(){
handler.removeCallbacks(handlerRunnable)
activeView = null
//reset the interval to avoid starting with the sped up interval
repeatInterval = interval
}
}