快速点击(点击)RecyclerView 打开多个 Fragments

Fast taps (clicks) on RecyclerView opens multiple Fragments

我已经为 RecyclerView 的 ViewHolder 实现了 onClick 侦听器

但是当我执行非常快速的双击或鼠标单击时,它会执行任务(在这种情况下打开一个单独的片段)两次或三次。

这是我的代码

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

关于如何防止此类行为的任何想法?

这是一个非常烦人的行为。我必须在工作中使用额外的标志来防止这种情况。

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

你可以这样修改。

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

此处最直接的方法是在 RecyclerView 中使用 setMotionEventSplittingEnabled(false)

RecyclerView中默认设置为真,允许处理多个触摸。

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

查看更多相关信息 here

在您的主题中添加以下属性

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

这将防止同时点击多个按钮。

  • 在适配器中创建布尔变量
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方法class:
public void setCanStart(boolean can){
    canStart = can;
}
  • 最后在片段或 Activity 中(适配器分配给 recyclerview 的地方)添加这个 onResume()
@Override
    public void onResume() {
        super.onResume();
        mAdapter.setCanStart(true);
    }

希望对您有所帮助:)

如果你使用的是 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) 

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

您可以 class 实施 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)));

我知道这已经晚了,答案已经给出了,但我发现我的案例的这个类似问题是由于第三方库 Material Ripple Layout。默认情况下,它启用对 onClick 的延迟调用,并允许对 onClick 发出多个请求,因此当动画完成时,所有这些点击都会立即注册并打开多个对话框。

此设置取消了延迟并解决了我的问题。

app:mrl_rippleDelayClick="false"

为时已晚,但它可以为其他人工作:

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
}

对我有用,不知道对不对。

快速点击(点击)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。

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

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

     recyclerView.setMotionEventSplittingEnabled(false);

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