Как избежать одновременного нажатия нескольких кнопок в Android?
Я использую две кнопки в поле зрения. При одновременном нажатии двух кнопок происходит переход к разным действиям одновременно. Как этого избежать?
Я пытался так, но это не работает, пожалуйста, сохраните....
public class MenuPricipalScreen extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.menu_principal_layout);
findViewById(R.id.imageView2).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
disable(findViewById(R.id.imageView3));
Intent intent = new Intent(MenuPricipalScreen.this,
SelectYourLanguageVideo.class);
startActivity(intent);
}
});
findViewById(R.id.imageView3).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
disable(findViewById(R.id.imageView2));
Intent intent = new Intent(MenuPricipalScreen.this,
CategoryScreen.class);
intent.putExtra("request", "false");
startActivity(intent);
}
});
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
((ImageView) findViewById(R.id.imageView3)).setEnabled(true);
((ImageView) findViewById(R.id.imageView2)).setEnabled(true);
((ImageView) findViewById(R.id.imageView3)).setClickable(true);
((ImageView) findViewById(R.id.imageView2)).setClickable(true);
((ImageView) findViewById(R.id.imageView3)).setFocusable(true);
((ImageView) findViewById(R.id.imageView2)).setFocusable(true);
}
private void disable(View v) {
Log.d("TAG", "TAG" + v.getId());
v.setEnabled(false);
v.setClickable(false);
v.setFocusable(false);
}
}
Спасибо,
17 ответов
Стандартный способ избежать нескольких нажатий - сохранить время последнего нажатия и избежать других нажатий кнопок в течение 1 секунды (или любого промежутка времени). Пример:
// Make your activity class to implement View.OnClickListener
public class MenuPricipalScreen extends Activity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
// setup listeners.
findViewById(R.id.imageView2).setOnClickListener(MenuPricipalScreen.this);
findViewById(R.id.imageView3).setOnClickListener(MenuPricipalScreen.this);
...
}
.
.
.
// variable to track event time
private long mLastClickTime = 0;
// View.OnClickListener.onClick method defination
@Override
public void onClick(View v) {
// Preventing multiple clicks, using threshold of 1 second
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
// Handle button clicks
if (v == R.id.imageView2) {
// Do your stuff.
} else if (v == R.id.imageView3) {
// Do your stuff.
}
...
}
.
.
.
}
Вы можете отключить мультитач в вашем приложении, используя это android:splitMotionEvents="false"
а также android:windowEnableSplitTouch="false"
в твоей теме.
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
...
<item name="android:splitMotionEvents">false</item>
<item name="android:windowEnableSplitTouch">false</item>
</style>
Дешевое решение:
У вас нет правильного разделения проблем (MVP или любой другой вариант), поэтому вы помещаете свой код в свою деятельность / фрагмент
Если вы не можете справиться с этим правильным образом, по крайней мере, не используйте недетерминированные решения (например, таймер).
Используйте инструменты, которые у вас уже есть, скажем, у вас есть этот код:
//Somewhere in your onCreate()
Button myButton = findViewById…
myButton.setOnClickListener(this);
// Down below…
@Override
public void onClick(View view) {
if (myButton.isEnabled()) {
myButton.setEnabled(false);
// Now do something like…
startActivity(…);
}
}
Теперь... в совершенно другом месте вашей логики, например... например, в onCreate или onResume, или в любом месте, где вы знаете, что хотите, чтобы кнопка снова работала...
myButton.setEnabled(true);
"Более современный" подход:
- Сделайте то же самое, но поместите логику в своего докладчика.
- Ваш докладчик решит, было ли запущено действие "кнопка".
- Ваш докладчик скажет свой "вид":
enableMyButton();
или жеdisableMyButton()
в зависимости. - Ваше мнение будет делать правильно.
- Вы знаете... основное разделение проблем.
ПОЧЕМУ "включен (истина / ложь)"?
Потому что она встроена. Потому что кнопка будет уважать свое состояние (и если у вас есть правильный список состояний, она изменит ваш внешний вид, и потому что она всегда будет соответствовать вашим ожиданиям). Кроме того, потому что легче протестировать докладчика, полного ложных заявлений, чем полную активность, которая может постоянно расти в коде.
Если вы используете kotlin, создайте развлечение следующим образом:
fun View.clickWithDebounce(debounceTime: Long = 1200L, 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()
}
})
}
Теперь в любом представлении просто позвоните:
view.clickWithDebounce{
...
}
Для пользователей Kotlin
object AppUtil {
var mLastClickTime=0L
fun isOpenRecently():Boolean{
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000){
return true
}
mLastClickTime = SystemClock.elapsedRealtime()
return false
}
}
В вашей деятельности или фрагменте или где угодно
просто добавьте это условие одной строки
if(isOpenRecently()) return
пример:
fun startHomePage(activity: Activity){
if(isOpenRecently()) return //this one line enough
val intent= Intent(activity,MainActivity::class.java)
activity.startActivity(intent)
}
Простой способ сделать это в Kotlin - использовать:
//When you need to disable the button
btn.isEnabled = false
//When you need to enable the button again
btn.isEnabled = true
Для любого, кто использует привязку данных:
@BindingAdapter("onClickWithDebounce")
fun onClickWithDebounce(view: View, listener: android.view.View.OnClickListener) {
view.setClickWithDebounce {
listener.onClick(view)
}
}
object LastClickTimeSingleton {
var lastClickTime: Long = 0
}
fun View.setClickWithDebounce(action: () -> Unit) {
setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
if (SystemClock.elapsedRealtime() - LastClickTimeSingleton.lastClickTime < 500L) return
else action()
LastClickTimeSingleton.lastClickTime = SystemClock.elapsedRealtime()
}
})
}
<androidx.appcompat.widget.AppCompatButton
..
android:text="@string/signup_signin"
app:onClickWithDebounce="@{() -> viewModel.onSignUpClicked()}"
... />
Вы можете попробовать мою крошечную библиотеку, она предоставляет то, что вам нужно, используя тот же подход, что и решение Shivanand. https://github.com/RexLKW/SClick
Если вам нужно сделать то же самое между объектами в
RecyclerView
, вы можете просто добавить
android:splitMotionEvents="false"
на ваш
View
внутри файла xml. Именно так:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/yourRecViewID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layoutAnimation="@anim/layout_animation"/>
Вот класс, который отбрасывает клики для View
а также MenuItem
,
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
/**
* Debounce's click events to prevent multiple rapid clicks.
* <p/>
* When a view is clicked, that view and any other views that have applied {@link #shareDebounce} to them,
* will have subsequent clicks ignored for the set {@link #DEBOUNCE_DURATION_MILLIS duration}.
*/
public final class ClickEvent {
private static final long DEBOUNCE_DURATION_MILLIS = 1000L;
private long debounceStartTime = 0;
/**
* Wraps the provided {@link OnClickListener OnClickListener} in a {@link ClickEvent}
* that will prevent multiple rapid clicks from executing.
* <p/>
* Usage:
* <pre>View.setOnClickListener(ClickEvent.debounce((OnClickListener) v -> // click listener runnable))</pre>
*/
public static OnClickListener debounce(@NonNull OnClickListener onClickListener) {
return new ClickEvent().wrapOnClickListener(onClickListener);
}
/**
* Wraps the provided {@link OnMenuItemClickListener OnMenuItemClickListener} in a
* that will prevent multiple rapid clicks from executing.
* <p/>
* Usage:
* <pre>MenuItem.setOnClickListener(ClickEvent.debounce((OnMenuItemClickListener) v -> // click listener runnable))</pre>
*/
public static OnMenuItemClickListener debounce(@NonNull OnMenuItemClickListener onClickListener) {
return new ClickEvent().wrapOnClickListener(onClickListener);
}
/**
* Allows the debounce to be shared between views to prevent multiple rapid clicks between views.
* <p/>
* Usage:
* <pre>
* ClickEvent clickEvent = new ClickEvent();
* View1.setOnClickListener(clickEvent.shareDebounce((OnClickListener) v -> // click listener runnable for View1))
* View2.setOnClickListener(clickEvent.shareDebounce((OnClickListener) v -> // click listener runnable for View2))
* </pre>
*/
public OnClickListener shareDebounce(@NonNull OnClickListener listener) {
return wrapOnClickListener(listener);
}
/**
* Allows the debounce to be shared between views to prevent multiple rapid clicks between views.
* Usage:
* <pre>
* ClickEvent clickEvent = new ClickEvent();
* MenuItem1.setOnClickListener(clickEvent.shareDebounce((OnMenuItemClickListener) v -> // click listener runnable for MenuItem1))
* MenuItem2.setOnClickListener(clickEvent.shareDebounce((OnMenuItemClickListener) v -> // click listener runnable for MenuItem2))
* </pre>
*/
public OnMenuItemClickListener shareDebounce(@NonNull OnMenuItemClickListener listener) {
return wrapOnClickListener(listener);
}
public void setDebounceStartTime() {
debounceStartTime = SystemClock.elapsedRealtime();
}
public boolean isThrottled() {
return SystemClock.elapsedRealtime() - debounceStartTime < DEBOUNCE_DURATION_MILLIS;
}
private OnClickListener wrapOnClickListener(@NonNull OnClickListener onClickListener) {
if (onClickListener instanceof OnThrottledClickListener) {
throw new IllegalArgumentException("Can't wrap OnThrottledClickListener!");
}
return new OnThrottledClickListener(this, onClickListener);
}
private OnMenuItemClickListener wrapOnClickListener(@NonNull OnMenuItemClickListener onClickListener) {
if (onClickListener instanceof OnThrottledClickListener) {
throw new IllegalArgumentException("Can't wrap OnThrottledClickListener!");
}
return new OnThrottledClickListener(this, onClickListener);
}
private static class OnThrottledClickListener implements OnClickListener, OnMenuItemClickListener {
private final ClickEvent clickEvent;
private OnClickListener wrappedListener;
private OnMenuItemClickListener wrappedMenuItemClickLister;
OnThrottledClickListener(@NonNull ClickEvent clickEvent, @NonNull OnClickListener onClickListener) {
this.clickEvent = clickEvent;
this.wrappedListener = onClickListener;
}
OnThrottledClickListener(@NonNull ClickEvent clickEvent, @NonNull OnMenuItemClickListener onClickListener) {
this.clickEvent = clickEvent;
this.wrappedMenuItemClickLister = onClickListener;
}
@Override
public void onClick(View v) {
if (clickEvent.isThrottled()) {
return;
}
wrappedListener.onClick(v);
clickEvent.setDebounceStartTime();
}
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
if (clickEvent.isThrottled()) {
return false;
}
clickEvent.setDebounceStartTime();
return wrappedMenuItemClickLister.onMenuItemClick(menuItem);
}
}
}
Поздно на вечеринку, но правильный способ сделать это:
myViewGroupOrRecycletView.isMotionEventSplittingEnabled = false
Вы можете использовать это с любой ViewGroup, содержащей ваши кнопки, или с RecyclerView, чтобы избежать одновременного выбора нескольких элементов.
В основном это позволяет избежать разделения событий движения для определенного вида.
Я нашел собственное решение этой проблемы. Хотя пользователь нажимает кнопку (нажимает спам), она все равно срабатывает один раз.
Это для Kotlin, поскольку я буду использовать Coroutine. Как только мы нажимаем кнопку, мы отключаем ее, а затем, когда работа завершена, мы снова включаем кнопку.
fun onSignInClick(view: View) {
view.isEnabled = false
val act = CoroutineScope(Dispatchers.Main).launch {
val intent = Intent(this@LoginActivity, MainActivity::class.java)
startActivity(intent)
}
if (act.isCompleted) {
view.isEnabled = true
}
}
Первый :
public class ClickValidate {
public static long lastClickTime;
public static boolean isValid()
{
long current=System.currentTimeMillis();
long def = current - lastClickTime;
if (def>1000)
{
lastClickTime = System.currentTimeMillis();
return true;
}
else
return false;
}
}
Теперь просто вызовите этот метод повсюду в теле метода onCLick Или где угодно:
if (ClickValidate.isValid()){
//your code
}
"Лучшая" практика - использовать onClickListener следующим образом ->
OnClickListener switchableListener = new OnClickListener(@Override
public void onClick(View arg0) {
arg0.setOnClickListener(null);
(or) use the view directly as a final variable in case of errors
ex :
textView.setOnClickListener(null);
// Some processing done here
Completed , enable the onclicklistener arg0.setOnClickListener(switchableListener);
});
Это должно решить проблему, и это довольно простой способ решения проблем.
Мое предложение было бы:
- вызов
setClickable(false)
для всех кнопок после нажатия одной из них - вызвать следующее мероприятие с помощью
startActivityForResult(...)
- отвергать
onActivityResult(...)
и позвониsetClickable(true)
для всех кнопок внутри него
Для пользователей Xamarin я создал решение, которое подклассифицирует класс кнопки:
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Widget;
using System;
using System.Threading.Tasks;
namespace MyProject.Droid.CustomWidgets
{
public class ButtonSingleClick : Button
{
private bool _clicked = false;
public int _timer = 700;
public new EventHandler Click;
protected ButtonSingleClick(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public ButtonSingleClick(Context context) : base(context)
{
base.Click += SingleClick;
}
public ButtonSingleClick(Context context, IAttributeSet attrs) : base(context, attrs)
{
base.Click += SingleClick;
}
public ButtonSingleClick(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
base.Click += SingleClick;
}
public ButtonSingleClick(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
base.Click += SingleClick;
}
private void SingleClick(object sender, EventArgs e)
{
if (!_clicked)
{
_clicked = true;
Click?.Invoke(this, e);
Task.Run(async delegate
{
await Task.Delay(_timer);
_clicked = false;
});
}
}
}
}
Я предлагаю вам при каждом нажатии кнопки перед выполнением любого другого кода использовать метод hasFocus(). Только один вид может иметь фокус в любой момент времени, поэтому это должно помешать вам нажать на оба