我的应用有问题,如果用户快速多次点击按钮,那么即使我按下按钮的对话框消失,也会生成多个事件
通过在单击按钮时将布尔变量设置为标志,我知道了一种解决方案,因此可以阻止将来的点击,直到关闭对话框。但是我有很多按钮,每次按钮都必须这样做,因为每个按钮似乎都是一种矫枉过正。在Android(或者更智能的解决方案)中,没有其他方法只允许每个按钮点击生成事件动作吗?
更糟糕的是,即使处理第一个操作,多个快速点击似乎也会生成多个事件操作,因此如果我想在第一个单击处理方法中禁用该按钮,则队列中已存在已存在的事件操作等待处理!
请帮忙 感谢
答案 0 :(得分:91)
这是我最近写的'debounced'onClick监听器。
您可以告诉它点击之间的最小可接受毫秒数。
在onDebouncedClick
而不是onClick
import android.os.SystemClock;
import android.view.View;
import java.util.Map;
import java.util.WeakHashMap;
/**
* A Debounced OnClickListener
* Rejects clicks that are too close together in time.
* This class is safe to use as an OnClickListener for multiple views, and will debounce each one separately.
*/
public abstract class DebouncedOnClickListener implements View.OnClickListener {
private final long minimumInterval;
private Map<View, Long> lastClickMap;
/**
* Implement this in your subclass instead of onClick
* @param v The view that was clicked
*/
public abstract void onDebouncedClick(View v);
/**
* The one and only constructor
* @param minimumIntervalMsec The minimum allowed time between clicks - any click sooner than this after a previous click will be rejected
*/
public DebouncedOnClickListener(long minimumIntervalMsec) {
this.minimumInterval = minimumIntervalMsec;
this.lastClickMap = new WeakHashMap<View, Long>();
}
@Override public void onClick(View clickedView) {
Long previousClickTimestamp = lastClickMap.get(clickedView);
long currentTimestamp = SystemClock.uptimeMillis();
lastClickMap.put(clickedView, currentTimestamp);
if(previousClickTimestamp == null || Math.abs(currentTimestamp - previousClickTimestamp.longValue()) > minimumInterval) {
onDebouncedClick(clickedView);
}
}
}
答案 1 :(得分:58)
使用RxBinding可以轻松完成。这是一个例子:
RxView.clicks(view).throttleFirst(500, TimeUnit.MILLISECONDS).subscribe(empty -> {
// action on click
});
在build.gradle
中添加以下行以添加RxBinding依赖项:
compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
答案 2 :(得分:16)
这是我接受的答案的版本。它非常相似,但不会尝试将视图存储在地图中,我认为这不是一个好主意。 它还添加了一个在许多情况下都很有用的包装方法。
/**
* Implementation of {@link OnClickListener} that ignores subsequent clicks that happen too quickly after the first one.<br/>
* To use this class, implement {@link #onSingleClick(View)} instead of {@link OnClickListener#onClick(View)}.
*/
public abstract class OnSingleClickListener implements OnClickListener {
private static final String TAG = OnSingleClickListener.class.getSimpleName();
private static final long MIN_DELAY_MS = 500;
private long mLastClickTime;
@Override
public final void onClick(View v) {
long lastClickTime = mLastClickTime;
long now = System.currentTimeMillis();
mLastClickTime = now;
if (now - lastClickTime < MIN_DELAY_MS) {
// Too fast: ignore
if (Config.LOGD) Log.d(TAG, "onClick Clicked too quickly: ignored");
} else {
// Register the click
onSingleClick(v);
}
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
public abstract void onSingleClick(View v);
/**
* Wraps an {@link OnClickListener} into an {@link OnSingleClickListener}.<br/>
* The argument's {@link OnClickListener#onClick(View)} method will be called when a single click is registered.
*
* @param onClickListener The listener to wrap.
* @return the wrapped listener.
*/
public static OnClickListener wrap(final OnClickListener onClickListener) {
return new OnSingleClickListener() {
@Override
public void onSingleClick(View v) {
onClickListener.onClick(v);
}
};
}
}
答案 3 :(得分:9)
您可以使用此项目:https://github.com/fengdai/clickguard 使用单个语句解决此问题:
ClickGuard.guard(button);
更新:不再推荐使用此库。我更喜欢尼基塔的解决方案。改为使用RxBinding。
答案 4 :(得分:5)
只需快速了解GreyBeardedGeek解决方案。更改if子句并添加Math.abs函数。设置如下:
if(previousClickTimestamp == null || (Math.abs(currentTimestamp - previousClickTimestamp.longValue()) > minimumInterval)) {
onDebouncedClick(clickedView);
}
用户可以更改Android设备上的时间并将其置于过去,因此如果没有这个,可能会导致错误。
PS:没有足够的分数来评论你的解决方案,所以我只是提出另一个答案。
答案 5 :(得分:5)
因此,此答案由ButterKnife库提供。
package butterknife.internal;
import android.view.View;
/**
* A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the
* same frame. A click on one button disables all buttons for that frame.
*/
public abstract class DebouncingOnClickListener implements View.OnClickListener {
static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = () -> enabled = true;
@Override public final void onClick(View v) {
if (enabled) {
enabled = false;
v.post(ENABLE_AGAIN);
doClick(v);
}
}
public abstract void doClick(View v);
}
此方法仅在处理完先前的点击后处理点击,并注意避免在框架中多次点击。
答案 6 :(得分:4)
这是一个简单的例子:
public abstract class SingleClickListener implements View.OnClickListener {
private static final long THRESHOLD_MILLIS = 1000L;
private long lastClickMillis;
@Override public void onClick(View v) {
long now = SystemClock.elapsedRealtime();
if (now - lastClickMillis > THRESHOLD_MILLIS) {
onClicked(v);
}
lastClickMillis = now;
}
public abstract void onClicked(View v);
}
答案 7 :(得分:4)
使用RxJava的类似解决方案
import android.view.View;
import java.util.concurrent.TimeUnit;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.subjects.PublishSubject;
public abstract class SingleClickListener implements View.OnClickListener {
private static final long THRESHOLD_MILLIS = 600L;
private final PublishSubject<View> viewPublishSubject = PublishSubject.<View>create();
public SingleClickListener() {
viewPublishSubject.throttleFirst(THRESHOLD_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<View>() {
@Override
public void call(View view) {
onClicked(view);
}
});
}
@Override
public void onClick(View v) {
viewPublishSubject.onNext(v);
}
public abstract void onClicked(View v);
}
答案 8 :(得分:3)
这可以用于任何事件,而不仅仅是点击。即使它是一系列快速事件(如rx debounce)的一部分,它也将发布最后一个事件。
class Debouncer(timeout: Long, unit: TimeUnit, fn: () -> Unit) {
private val timeoutMillis = unit.toMillis(timeout)
private var lastSpamMillis = 0L
private val handler = Handler()
private val runnable = Runnable {
fn()
}
fun spam() {
if (SystemClock.uptimeMillis() - lastSpamMillis < timeoutMillis) {
handler.removeCallbacks(runnable)
}
handler.postDelayed(runnable, timeoutMillis)
lastSpamMillis = SystemClock.uptimeMillis()
}
}
// example
view.addOnClickListener.setOnClickListener(object: View.OnClickListener {
val debouncer = Debouncer(1, TimeUnit.SECONDS, {
showSomething()
})
override fun onClick(v: View?) {
debouncer.spam()
}
})
1)在侦听器的一个字段中构造Debouncer但在回调函数之外,配置了超时和你想要限制的回调fn。
2)在监听器的回调函数中调用Debouncer的垃圾邮件方法。
答案 9 :(得分:1)
您可以为此目的使用Rxbinding3。只需在build.gradle中添加此依赖项
build.gradle
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
然后在您的活动或片段中,使用以下代码
your_button.clicks().throttleFirst(10000, TimeUnit.MILLISECONDS).subscribe {
// your action
}
答案 10 :(得分:1)
来自Signal App的基于Handler
的调节器。
import android.os.Handler;
import android.support.annotation.NonNull;
/**
* A class that will throttle the number of runnables executed to be at most once every specified
* interval.
*
* Useful for performing actions in response to rapid user input where you want to take action on
* the initial input but prevent follow-up spam.
*
* This is different from a Debouncer in that it will run the first runnable immediately
* instead of waiting for input to die down.
*
* See http://rxmarbles.com/#throttle
*/
public final class Throttler {
private static final int WHAT = 8675309;
private final Handler handler;
private final long thresholdMs;
/**
* @param thresholdMs Only one runnable will be executed via {@link #publish} every
* {@code thresholdMs} milliseconds.
*/
public Throttler(long thresholdMs) {
this.handler = new Handler();
this.thresholdMs = thresholdMs;
}
public void publish(@NonNull Runnable runnable) {
if (handler.hasMessages(WHAT)) {
return;
}
runnable.run();
handler.sendMessageDelayed(handler.obtainMessage(WHAT), thresholdMs);
}
public void clear() {
handler.removeCallbacksAndMessages(null);
}
}
用法示例:
throttler.publish(() -> Log.d("TAG", "Example"));
在OnClickListener
中的用法示例:
view.setOnClickListener(v -> throttler.publish(() -> Log.d("TAG", "Example")));
Kt用法示例:
view.setOnClickListener {
throttler.publish {
Log.d("TAG", "Example")
}
}
或带有扩展名:
fun View.setThrottledOnClickListener(throttler: Throttler, function: () -> Unit) {
throttler.publish(function)
}
然后是示例用法:
view.setThrottledOnClickListener(throttler) {
Log.d("TAG", "Example")
}
答案 11 :(得分:1)
我将这个类与数据绑定一起使用。效果很好。
/**
* This class will prevent multiple clicks being dispatched.
*/
class OneClickListener(private val onClickListener: View.OnClickListener) : View.OnClickListener {
private var lastTime: Long = 0
override fun onClick(v: View?) {
val current = System.currentTimeMillis()
if ((current - lastTime) > 500) {
onClickListener.onClick(v)
lastTime = current
}
}
companion object {
@JvmStatic @BindingAdapter("oneClick")
fun setOnClickListener(theze: View, f: View.OnClickListener?) {
when (f) {
null -> theze.setOnClickListener(null)
else -> theze.setOnClickListener(OneClickListener(f))
}
}
}
}
我的布局看起来像这样
<TextView
app:layout_constraintTop_toBottomOf="@id/bla"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center"
android:textSize="18sp"
app:oneClick="@{viewModel::myHandler}" />
答案 12 :(得分:0)
基于 @GreyBeardedGeek answer ,
debounceClick_last_Timestamp
上创建ids.xml
进行标记
上次点击时间戳。将此代码块添加到BaseActivity
protected void debounceClick(View clickedView, DebouncedClick callback){
debounceClick(clickedView,1000,callback);
}
protected void debounceClick(View clickedView,long minimumInterval, DebouncedClick callback){
Long previousClickTimestamp = (Long) clickedView.getTag(R.id.debounceClick_last_Timestamp);
long currentTimestamp = SystemClock.uptimeMillis();
clickedView.setTag(R.id.debounceClick_last_Timestamp, currentTimestamp);
if(previousClickTimestamp == null
|| Math.abs(currentTimestamp - previousClickTimestamp) > minimumInterval) {
callback.onClick(clickedView);
}
}
public interface DebouncedClick{
void onClick(View view);
}
用法:
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
debounceClick(v, 3000, new DebouncedClick() {
@Override
public void onClick(View view) {
doStuff(view); // Put your's click logic on doStuff function
}
});
}
});
使用lambda
view.setOnClickListener(v -> debounceClick(v, 3000, this::doStuff));
答案 13 :(得分:0)
在此处放置一个示例
SideBar
view.safeClick { doSomething() }
答案 14 :(得分:0)
这是一个非常简单的解决方案,可以与lambda一起使用:
view.setOnClickListener(new DebounceClickListener(v -> this::doSomething));
以下是可复制/粘贴的代码段:
public class DebounceClickListener implements View.OnClickListener {
private static final long DEBOUNCE_INTERVAL_DEFAULT = 500;
private long debounceInterval;
private long lastClickTime;
private View.OnClickListener clickListener;
public DebounceClickListener(final View.OnClickListener clickListener) {
this(clickListener, DEBOUNCE_INTERVAL_DEFAULT);
}
public DebounceClickListener(final View.OnClickListener clickListener, final long debounceInterval) {
this.clickListener = clickListener;
this.debounceInterval = debounceInterval;
}
@Override
public void onClick(final View v) {
if ((SystemClock.elapsedRealtime() - lastClickTime) < debounceInterval) {
return;
}
lastClickTime = SystemClock.elapsedRealtime();
clickListener.onClick(v);
}
}
享受!
答案 15 :(得分:0)
我们可以在没有任何库的情况下做到这一点。只需创建一个扩展功能:
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) {
this.setOnClickListener(object : View.OnClickListener {
private var lastClickTime: Long = 0
override fun onClick(v: View) {
if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
else action()
lastClickTime = SystemClock.elapsedRealtime()
}
})
}
使用以下代码查看onClick:
buttonShare.clickWithDebounce {
// Do anything you want
}
答案 16 :(得分:0)
我的解决方案,当我们从片段和活动中退出(破坏)时,需要调用<div className="row">
<div className="col-md-4"></div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
</div>
:
removeall
答案 17 :(得分:0)
我想说的最简单的方法是使用KProgressHUD之类的“加载”库。
https://github.com/Kaopiz/KProgressHUD
onClick方法的第一件事是调用加载动画,该动画会立即阻止所有UI,直到开发人员决定释放它为止。
因此,您可以在onClick动作中使用它(使用Butterknife,但显然可以使用任何方法):
此外,不要忘记单击后禁用按钮。
@OnClick(R.id.button)
void didClickOnButton() {
startHUDSpinner();
button.setEnabled(false);
doAction();
}
然后:
public void startHUDSpinner() {
stopHUDSpinner();
currentHUDSpinner = KProgressHUD.create(this)
.setStyle(KProgressHUD.Style.SPIN_INDETERMINATE)
.setLabel(getString(R.string.loading_message_text))
.setCancellable(false)
.setAnimationSpeed(3)
.setDimAmount(0.5f)
.show();
}
public void stopHUDSpinner() {
if (currentHUDSpinner != null && currentHUDSpinner.isShowing()) {
currentHUDSpinner.dismiss();
}
currentHUDSpinner = null;
}
如果愿意,可以在doAction()方法中使用stopHUDSpinner方法:
private void doAction(){
// some action
stopHUDSpinner()
}
根据您的应用逻辑重新启用按钮: button.setEnabled(true);
答案 18 :(得分:0)
处理这种情况的更重要的方法是将Throttling运算符(Throttle First)与RxJava2一起使用。 在Kotlin中实现这一目标的步骤:
1)。依赖关系:-在build.gradle应用级文件中添加rxjava2依赖关系。
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.10'
2)。构造一个实现View.OnClickListener的抽象类,并包含节流优先运算符来处理视图的OnClick()方法。代码段为:
import android.util.Log
import android.view.View
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.subjects.PublishSubject
import java.util.concurrent.TimeUnit
abstract class SingleClickListener : View.OnClickListener {
private val publishSubject: PublishSubject<View> = PublishSubject.create()
private val THRESHOLD_MILLIS: Long = 600L
abstract fun onClicked(v: View)
override fun onClick(p0: View?) {
if (p0 != null) {
Log.d("Tag", "Clicked occurred")
publishSubject.onNext(p0)
}
}
init {
publishSubject.throttleFirst(THRESHOLD_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { v -> onClicked(v) }
}
}
3)。在活动中单击视图时实现此SingleClickListener类。可以通过以下方式实现:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val singleClickListener = object : SingleClickListener(){
override fun onClicked(v: View) {
// operation on click of xm_view_id
}
}
xm_viewl_id.setOnClickListener(singleClickListener)
}
在应用程序中执行上述步骤可以简单地避免多次单击视图直到600mS。 编码愉快!
答案 19 :(得分:0)
这就像这样解决了
Observable<Object> tapEventEmitter = _rxBus.toObserverable().share();
Observable<Object> debouncedEventEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS);
Observable<List<Object>> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEventEmitter);
debouncedBufferEmitter.buffer(debouncedEventEmitter)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Object>>() {
@Override
public void call(List<Object> taps) {
_showTapCount(taps.size());
}
});