Быстрые нажатия (щелчки) на RecyclerView открывают несколько фрагментов

Я внедрил прослушиватель onClick в свой ViewHolder для моего RecyclerView

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

вот мой код

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    TextView tvTitle, tvDescription;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setClickable(true);
        itemView.setOnClickListener(this);

        tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
        tvDescription = (TextView) itemView.findViewById(R.id.tv_description);
    }

    @Override
    public void onClick(View v) {
        mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open FRAGMENT_VIEW
    }
}

Любые идеи о том, как предотвратить такое поведение?

Ответ 1

Вы можете изменить его так.

public class ViewHolder extends RecyclerView.ViewHolder implements
        View.OnClickListener {
    TextView tvTitle, tvDescription;
    private long mLastClickTime = System.currentTimeMillis();
    private static final long CLICK_TIME_INTERVAL = 300;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setClickable(true);
        itemView.setOnClickListener(this);

        tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
        tvDescription = (TextView) itemView
                .findViewById(R.id.tv_description);
    }

    @Override
    public void onClick(View v) {
        long now = System.currentTimeMillis();
        if (now - mLastClickTime < CLICK_TIME_INTERVAL) {
            return;
        }
        mLastClickTime = now;
        mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open
                                                                // FRAGMENT_VIEW
    }
}

Ответ 2

Самый простой подход здесь будет использовать setMotionEventSplittingEnabled(false) в вашем RecyclerView.

По умолчанию для параметра RecyclerView установлено значение true, что позволяет обрабатывать несколько касаний.

Если установлено значение false, этот метод ViewGroup предотвращает получение детьми RecyclerView нескольких кликов, а только обработку первого.

Подробнее об этом здесь.

Ответ 3

Это очень неприятное поведение. Я должен использовать дополнительный флаг, чтобы предотвратить это в моей работе.

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tvTitle, tvDescription;
private boolean clicked;

public ViewHolder(View itemView) {
    super(itemView);
    itemView.setClickable(true);
    itemView.setOnClickListener(this);

    tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
    tvDescription = (TextView) itemView.findViewById(R.id.tv_description);
}

@Override
public void onClick(View v) {
    if(clicked){
        return;
    }
    clicked = true;
    v.postDelay(new Runnable(){
          @Override
          public void run(View v){
              clicked = false;
          }
    },500);
    mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open FRAGMENT_VIEW
}
}

Ответ 4

  • Создайте логическую переменную в адаптере
boolean canStart = true;
  • Сделайте OnClickListener похожим на
ViewHolder.dataText.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (canStart) {
            canStart = false; // do canStart false 
            // Whatever you want to do and not have run twice due to double tap
        }
    }
}
  • Добавьте метод setCanStart в класс адаптера:
public void setCanStart(boolean can){
    canStart = can;
}
  • Наконец, во фрагменте или "Деятельности" (где адаптер назначен представлению реселлера) добавьте этот onResume()
@Override
    public void onResume() {
        super.onResume();
        mAdapter.setCanStart(true);
    }

Надеюсь, это поможет :)

Ответ 5

Если вы используете Kotlin, вы можете пойти с этим на основе ответа денег

class CodeThrottle {
    companion object {
        const val MIN_INTERVAL = 300
    }
    private var lastEventTime = System.currentTimeMillis()

    fun throttle(code: () -> Unit) {
        val eventTime = System.currentTimeMillis()
        if (eventTime - lastEventTime > MIN_INTERVAL) {
            lastEventTime = eventTime
            code()
        }
    }
}

Создайте этот объект в вашем держателе просмотра

    private val codeThrottle = CodeThrottle()

Затем сделайте следующее в вашем bind

name.setOnClickListener { codeThrottle.throttle { listener.onCustomerClicked(customer, false) } }

Поместите любой код, который вам нужен, вместо

listener.onCustomerClicked(customer, false) 

Ответ 6

Добавьте в свою тему атрибуты

<item name="android:splitMotionEvents">false</item>
<item name="android:windowEnableSplitTouch">false</item>

Это предотвратит одновременное нажатие нескольких кранов.

Ответ 7

Я повторно использовал DebouncingOnClickListener из Butterknife для отмены кликов в течение определенного времени, в дополнение к предотвращению кликов по нескольким представлениям.

Чтобы использовать, расширить его и реализовать doOnClick.

DebouncingOnClickListener.kt

import android.view.View

/**
 * A [click listener][View.OnClickListener] that debounces multiple clicks posted in the
 * same frame and within a time frame. A click on one view disables all view for that frame and time
 * span.
 */
abstract class DebouncingOnClickListener : View.OnClickListener {

    final override fun onClick(v: View) {
        if (enabled && debounced) {
            enabled = false
            lastClickTime = System.currentTimeMillis()
            v.post(ENABLE_AGAIN)
            doClick(v)
        }
    }

    abstract fun doClick(v: View)

    companion object {
        private const val DEBOUNCE_TIME_MS: Long = 1000

        private var lastClickTime = 0L // initially zero so first click isn't debounced

        internal var enabled = true
        internal val debounced: Boolean
            get() = System.currentTimeMillis() - lastClickTime > DEBOUNCE_TIME_MS

        private val ENABLE_AGAIN = { enabled = true }
    }
}

Ответ 8

Вы можете сделать класс, реализующий View.OnClickListener

public class DoubleClickHelper implements View.OnClickListener {

    private long mLastClickTime = System.currentTimeMillis();
    private static final long CLICK_TIME_INTERVAL = 300;
    private Callback callback;

    public DoubleClickHelper(Callback callback) {
        this.callback = callback;
    }

    @Override
    public void onClick(View v) {
        long now = System.currentTimeMillis();
        if (now - mLastClickTime < CLICK_TIME_INTERVAL) {
            return;
        }
        mLastClickTime = now;
        callback.handleClick();
    }

    public interface Callback {
        void handleClick();
    }
}

И чем использовать это как:

ivProduct.setOnClickListener(new DoubleClickHelper(() -> listener.onProductInfoClick(wItem)));