Fast taps (clicks) on RecyclerView opens multiple Fragments

时间:2015-08-07 02:40:58

标签: android android-fragments onclicklistener android-recyclerview android-adapter

I have implemented onClick listener to my ViewHolder for my RecyclerView

But when I perform very fast double taps or mouse clicks, it performs the task (opens a seperate fragment in this case) twice or three times.

here is my code

    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
    }
}

Any ideas on how to prevent such behaviour?

11 个答案:

答案 0 :(得分:14)

您可以像这样修改它。

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
    }
}

答案 1 :(得分:12)

这里最直接的方法是在中使用setMotionEventSplittingEnabled(false)

默认情况下,RecyclerView中设置为true,允许处理多个触摸。

设置为false时,此RecyclerView方法会阻止ViewGroup个孩子收到多次点击,只会处理第一个。

详细了解此here

答案 2 :(得分:6)

这是一种非常讨厌的行为。我必须使用额外的标志来阻止我的工作。

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
}
}

答案 3 :(得分:1)

  • 在Adapter
  • 中创建一个布尔变量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 } } }

  • 在Adapter类中添加setCanStart方法:

    public void setCanStart(boolean can){ canStart = can; }

  • 最后在片段或活动中(将适配器分配给recyclerview)添加此onResume()

    @Override public void onResume() { super.onResume(); mAdapter.setCanStart(true); }

希望它会有所帮助:)

答案 4 :(得分:1)

如果您使用的是Kotlin,则可以根据Money的答案使用它

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()

然后在绑定中执行以下操作

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

放置所需的任何代码来代替

listener.onCustomerClicked(customer, false) 

答案 5 :(得分:0)

在主题中添加以下属性

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

这样可以防止多次敲击。

答案 6 :(得分:0)

除了防止在多个视图上单击之外,我还重新指定了Butterknife中的DebouncingOnClickListener来在指定时间内消除点击次数。

要使用,请对其进行扩展并实现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 }
    }
}

答案 7 :(得分:0)

您可以使类实现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)));

答案 8 :(得分:0)

我知道这很晚了,并且已经给出了答案,但是我发现我的案例中的这个类似问题是由于第三方库Material Ripple Layout引起的。默认情况下,它启用对onClick的延迟调用,并允许对onClick进行多次请求,以便在动画结束时立即注册所有这些单击并打开多个对话框。

此设置可以消除延迟并为我解决问题。

app:mrl_rippleDelayClick="false"

答案 9 :(得分:0)

为时已晚,但可以为其他人使用:

recyclerAdapter.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            int id = ....
            if(id == 1){
                view.setClickable(false); //add this
                Intent a = new Intent...
                startActivity(a);

            }else if(id == 2){
                view.setClickable(false);
                Intent b = ...
                startActivity(b);
            }
        }
    });

片段-onResume()

@Override
public void onResume() {
    super.onResume();
    Objects.requireNonNull(getActivity()).invalidateOptionsMenu();
    recyclerView.setAdapter(recyclerAdapter); //add this
}

它对我有用,我不知道它是否正确。

答案 10 :(得分:0)

在 RecyclerView 上快速点击(点击)会导致两种情况-

  1. RecyclerView 的单个项目被多次点击。 这可能会导致多次创建目标片段,从而使单个片段多次堆叠,从而破坏用户的流畅体验。

  2. 一次点击多个 RecyclerView 项目。 这可能会导致应用程序出现不良行为。 (再次打开多个片段。)

应该处理这两种情况以获得正确的应用运行体验。 为了防止第一种情况,您可以使用这样的逻辑,即在一定的时间间隔内,如果项目被多次单击,则不应创建新片段。 这是下面的代码-

    class ViewHolder extends RecyclerView.ViewHolder{
     //Suppose your item is a CardView
     private CardView cardView;
      private static final long TIME_INTERVAL_GAP=500;
      private long lastTimeClicked=System.currentTimeMillis();
      public ViewHolder(@NonNull View itemView)
        {
        cardView=itemView.findViewById(R.id.card_view);
        
          cardView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            long now=System.currentTimeMillis();
            //check if cardView is clicked again within the time interval gap
            if(now-lastTimeClicked<TIME_INTERVAL_GAP)
                return;       //no action to perform if it is within the interval gap.
            mLastClickTime=now;
            //... Here your code to open a new fragment  
             }
         });
         
         }

     
      }

第二种情况的解决方案- RecyclerView 有一个方法 setMotionEventSplittingEnabled(boolean split)

文档说-

在触摸事件分派期间启用或禁用将 MotionEvent 拆分为多个子项。默认情况下,针对面向 HONEYCOMB 或更新的 SDK 版本。

启用此选项后,MotionEvent 可能会根据每个指针最初下降的位置拆分并分派到不同的子视图。这允许用户交互,例如独立滚动两个内容窗格、按钮的和弦以及在不同的内容片段上执行独立的手势。 Split 设置为 true 以允许 MotionEvents 被拆分并分派到多个子视图。并将其设置为 false 以仅允许一个子视图作为目标。

所以在你的代码中添加一行代码-

     recyclerView.setMotionEventSplittingEnabled(false);

这些肯定会解决因快速点击 RecyclerView 而导致的问题,并防止您的应用不必要地堆叠相同的片段。