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?
答案 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)
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 上快速点击(点击)会导致两种情况-
RecyclerView 的单个项目被多次点击。 这可能会导致多次创建目标片段,从而使单个片段多次堆叠,从而破坏用户的流畅体验。
一次点击多个 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 而导致的问题,并防止您的应用不必要地堆叠相同的片段。