【问题标题】:Fast taps (clicks) on RecyclerView opens multiple FragmentsRecyclerView 上的快速点击(点击)会打开多个 Fragment
【发布时间】:2015-10-30 09:01:05
【问题描述】:

我已经为我的 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
    }
}

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

【问题讨论】:

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


    【解决方案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
        }
    }
    

    【讨论】:

    • 这个问题让我头疼了几分钟。谢谢老兄!
    • 答案很好,但它只解决了触摸相同项目的情况下的问题。我的意思是,如果用户触摸 item1 并立即触摸 item2,则会打开两个片段。至少,这发生在我身上。
    • @Roger 你实施了什么解决方案?我正在考虑使用 rxJava 中的 debounce 或 throttleFirst ,但这可以解决特定视图的问题。
    • @Roger 将点击逻辑移动到适配器中以处理对列表中每个项目的点击:将接口传递给 viewHolder 并在每个点击侦听器上调用它。在适配器实现界面中,使用 CLICK_TIME_INTERVAL 防止多次点击
    【解决方案2】:

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

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

    当设置为 false 时,此 ViewGroup 方法防止 RecyclerView 子级接收多次点击,只处理第一次。

    查看更多关于这个here的信息。

    【讨论】:

    • 简短而甜蜜的解决方案。
    • 这不是真的,内部文档证实了这一点: * @param split true 允许将 MotionEvents 拆分并分派到多个 * 子视图。 false 只允许一个子视图成为此 ViewGroup 接收到的任何 MotionEvent 的目标。简单来说,设置这个只会阻止多个触摸事件被不同的child处理,但是一个child仍然可以接收到很多触摸事件。 (或者更简单的说,点击a->b不可能,但是a->双击还是可以的)
    • 真棒又短又甜
    • setMotionEventSplittingEnabled(false) 像魅力一样的工作:)
    【解决方案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
    }
    }
    

    【讨论】:

    • 谢谢,你的概念是正确的,它有效,但必须有一些适当的方法来处理它,我还不知道,还是再次感谢。
    • 使用 --> new Handler().postDelayed(new Runnable() {...} 而不是 v.postDelay(new Runnable(){...}
    • 小改动 if(clicked){ return; } 点击=真; v.postDelayed(new Runnable(){ @Override public void run() { clicked = false; } },500);
    • 我认为这不是一个好方法,较慢的设备可能无法在半秒内完成点击事件,具体取决于点击事件中发生的情况,这将导致同样的问题。
    【解决方案4】:

    如果您使用的是 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】:

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

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

      这将防止同时点击多个。

      【讨论】:

        【解决方案6】:
        • 在 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;
        }
        
        • 最后在 Fragment 或 Activity(适配器分配给 recyclerview 的地方)添加这个 onResume()
        @Override
            public void onResume() {
                super.onResume();
                mAdapter.setCanStart(true);
            }
        

        希望它会有所帮助:)

        【讨论】:

        • 这不起作用,因为 dispatchTouchEvent 将下一个点击处理程序排队并在大多数系统有机会将其点击处理程序中的布尔值设置为 true 之前运行它,所以这不会在许多甚至大多数情况下都是可行的选择。
        【解决方案7】:

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

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

        app:mrl_rippleDelayClick="false"
        

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

            你可以让类实现 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)));
            

            【讨论】:

              【解决方案10】:

              为时已晚,但对其他人有用:

              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
              }
              

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

              【讨论】:

              • 这是一个非常具体的解决方案,也有更好的解决方案。
              【解决方案11】:

              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 拆分为多个子级。默认情况下,针对目标应用程序启用此行为 SDK 版本的 HONEYCOMB 或更高版本。

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

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

                   recyclerView.setMotionEventSplittingEnabled(false);
              

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-09-07
                • 2016-02-27
                • 2016-02-01
                • 2021-11-22
                • 1970-01-01
                • 2015-08-02
                • 1970-01-01
                • 2013-05-08
                相关资源
                最近更新 更多