快速点击(点击)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 会导致两种情况-
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。
启用此选项后,MotionEvents 可能会被拆分并分派到不同的子视图,具体取决于每个指针最初下降的位置。这允许用户交互,例如独立滚动两个内容窗格、按钮的和弦以及在不同的内容片段上执行独立的手势。 Split 设置为 true 以允许将 MotionEvents 拆分并分派到多个子视图。并将其设置为 false 以仅允许一个子视图作为目标。
所以在你的代码中只需添加一行代码-
recyclerView.setMotionEventSplittingEnabled(false);
这些肯定会解决由于快速点击 RecyclerView 而导致的问题,并防止您的应用不必要地堆叠相同的片段。
我已经为 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 会导致两种情况-
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。
启用此选项后,MotionEvents 可能会被拆分并分派到不同的子视图,具体取决于每个指针最初下降的位置。这允许用户交互,例如独立滚动两个内容窗格、按钮的和弦以及在不同的内容片段上执行独立的手势。 Split 设置为 true 以允许将 MotionEvents 拆分并分派到多个子视图。并将其设置为 false 以仅允许一个子视图作为目标。
所以在你的代码中只需添加一行代码-
recyclerView.setMotionEventSplittingEnabled(false);
这些肯定会解决由于快速点击 RecyclerView 而导致的问题,并防止您的应用不必要地堆叠相同的片段。