Как избежать одновременного нажатия нескольких кнопок в 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);

"Более современный" подход:

  1. Сделайте то же самое, но поместите логику в своего докладчика.
  2. Ваш докладчик решит, было ли запущено действие "кнопка".
  3. Ваш докладчик скажет свой "вид": enableMyButton(); или же disableMyButton() в зависимости.
  4. Ваше мнение будет делать правильно.
  5. Вы знаете... основное разделение проблем.

ПОЧЕМУ "включен (истина / ложь)"?

Потому что она встроена. Потому что кнопка будет уважать свое состояние (и если у вас есть правильный список состояний, она изменит ваш внешний вид, и потому что она всегда будет соответствовать вашим ожиданиям). Кроме того, потому что легче протестировать докладчика, полного ложных заявлений, чем полную активность, которая может постоянно расти в коде.

Если вы используете 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);
    });

Это должно решить проблему, и это довольно простой способ решения проблем.

Мое предложение было бы:

  1. вызов setClickable(false) для всех кнопок после нажатия одной из них
  2. вызвать следующее мероприятие с помощью startActivityForResult(...)
  3. отвергать 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(). Только один вид может иметь фокус в любой момент времени, поэтому это должно помешать вам нажать на оба

Другие вопросы по тегам