RecyclerViewのViewHolderにonClickリスナーを実装しました
しかし、非常に高速なダブルタップまたはマウスクリックを実行すると、タスクが2回または3回実行されます(この場合は別のフラグメントが開きます)。
これが私のコードです
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 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
でtrueに設定されており、複数のタッチを処理できます。
Falseに設定すると、このViewGroup
メソッドは、RecyclerView
の子が複数のクリックを受信するのを防ぎ、最初のクリックのみを処理します。
これについてもっと見る ここ 。
これは非常に迷惑な動作です。作業でこれを防ぐために、追加のフラグを使用する必要があります。
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
}
}
boolean canStart = true;
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
}
}
}
public void setCanStart(boolean can){
canStart = can;
}
@Override
public void onResume() {
super.onResume();
mAdapter.setCanStart(true);
}
それが役立つことを願っています:)
テーマに以下の属性を追加します
<item name="Android:splitMotionEvents">false</item>
<item name="Android:windowEnableSplitTouch">false</item>
これにより、同時に複数回タップするのを防ぐことができます。
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)
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)));
DebouncingOnClickListener をButterknifeから再利用して、複数のビューでのクリックを防ぐことに加えて、指定された時間内にクリックをデバウンスしました。
使用するには、それを拡張して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 }
}
}
これは遅れており、すでに回答が得られていることはわかっていますが、私の場合のこの同様の問題は、サードパーティのライブラリであるMaterial RippleLayoutが原因であることがわかりました。デフォルトでは、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
}
それは私のために働きます、私はそれが正しいかどうかわかりません。