Функция RxJava2 debounce не работает должным образом в RecyclerView-Android
Я пытаюсь создать пользовательский ImageButton, который будет накапливать клики и запускать событие, когда пользователь перестает нажимать кнопку в течение 1 секунды.
я использовал функцию debounce для выполнения этого.
пользовательский ImageButton:
public class MBImageButton extends ImageButton {
private AtomicInteger mCounter;
private Disposable mDisposable;
private Observable<Object> observable;
private OnAccumulatedRequestsRead mOnAccumulatedRequestsRead;
private OnEverClickListener mOnEverClickListener;
private int emitEveryMilli = 1000; // every 1 second by default
private boolean shouldDisposeOnDetachFromWindow = true;
public MBImageButton(Context context) {
super(context);
init();
}
public MBImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MBImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setAccumulatedClickListeners(OnEverClickListener onEverClickListener,
OnAccumulatedRequestsRead onAccumulatedRequestsRead) {
setOnAccumulatedRequestsRead(onAccumulatedRequestsRead);
setOnEverClickListener(onEverClickListener);
initClickObservable();
subscribe();
}
private void initClickObservable() {
observable = Observable.create(emitter -> {
emitter.setCancellable(() -> setOnClickListener(null));
try {
setOnClickListener(view -> {
try {
final int currentCount = mCounter.incrementAndGet();
Timber.d("Clicked: " + currentCount);
if (mOnEverClickListener != null) {
mOnEverClickListener.onEveryClickListener(currentCount);
}
emitter.onNext(new Object());
} catch (Exception e) {
emitter.onError(e);
}
});
} catch (Exception e) {
emitter.onError(e);
}
}).doOnSubscribe(disposable -> mDisposable = disposable)
.debounce(emitEveryMilli, TimeUnit.MILLISECONDS);
}
private void subscribe() {
observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> {
try {
final int count = mCounter.get();
if(count==0) return;
mCounter.set(0);
Timber.d("Accumulated Clicks: " + count);
if (mOnAccumulatedRequestsRead != null) {
mOnAccumulatedRequestsRead.onAccumulatedRequestsReady(count);
}
} catch (Exception e) {
Timber.e(e);
}
}, Timber::e);
}
private void init() {
mCounter = new AtomicInteger(0);
}
public void disposeAccumulatedClickListeners() {
if (mDisposable != null) {
mDisposable.dispose();
}
}
public void shouldDisposeOnDetachFromWindow(boolean shouldDisposeOnDetachFromWindow) {
this.shouldDisposeOnDetachFromWindow = shouldDisposeOnDetachFromWindow;
}
public void setEmitEveryMilliseconds(int emitEveryMilli) {
this.emitEveryMilli = emitEveryMilli;
initClickObservable();
subscribe();
}
private void setOnEverClickListener(OnEverClickListener onEverClickListener) {
mOnEverClickListener = onEverClickListener;
}
private void setOnAccumulatedRequestsRead(OnAccumulatedRequestsRead onAccumulatedRequestsRead) {
mOnAccumulatedRequestsRead = onAccumulatedRequestsRead;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (shouldDisposeOnDetachFromWindow) {
if (mDisposable != null) {
mDisposable.dispose();
}
}
}
public interface OnAccumulatedRequestsRead {
void onAccumulatedRequestsReady(int count);
}
public interface OnEverClickListener {
void onEveryClickListener(int currentCount);
}
}
функция, которая устанавливает этот imageButton для debouncing:
setAccumulatedClickListeners(OnEverClickListener, OnAccumulatedRequestsRead)
когда я настраиваю этот вид из RecyclerView
, все работает правильно,
результат выглядит так, когда я нажимаю 9 раз последовательно:
D/MBImageButton: Clicked: 1
D/MBImageButton: Clicked: 2
D/MBImageButton: Clicked: 3
D/MBImageButton: Clicked: 4
D/MBImageButton: Clicked: 5
D/MBImageButton: Clicked: 6
D/MBImageButton: Clicked: 7
D/MBImageButton: Clicked: 8
D/MBImageButton: Clicked: 9
D/MBImageButton: Accumulated Clicks: 9
когда я добавляю этот пользовательский ImageButton в ViewHolder RecyclerView, результат неверен для того же количества кликов:
D/MBImageButton: Clicked: 1
D/MBImageButton: Clicked: 2
D/MBImageButton: Clicked: 1
D/MBImageButton: Clicked: 3
D/MBImageButton: Clicked: 2
D/MBImageButton: Clicked: 4
D/MBImageButton: Accumulated Clicks: 4
D/MBImageButton: Clicked: 1
D/MBImageButton: Accumulated Clicks: 2
D/MBImageButton: Clicked: 2
D/MBImageButton: Accumulated Clicks: 2
D/MBImageButton: Accumulated Clicks: 0
D/MBImageButton: Clicked: 1
D/MBImageButton: Accumulated Clicks: 1
D/MBImageButton: Accumulated Clicks: 0
D/MBImageButton: Accumulated Clicks: 0
это еще один результат за 9 последовательных кликов:
D/MBImageButton: Clicked: 1
D/MBImageButton: Clicked: 2
D/MBImageButton: Clicked: 1
D/MBImageButton: Clicked: 3
D/MBImageButton: Clicked: 2
D/MBImageButton: Clicked: 4
D/MBImageButton: Clicked: 3
D/MBImageButton: Accumulated Clicks: 4
D/MBImageButton: Clicked: 1
D/MBImageButton: Accumulated Clicks: 3
D/MBImageButton: Accumulated Clicks: 1
D/MBImageButton: Accumulated Clicks: 0
D/MBImageButton: Clicked: 1
D/MBImageButton: Accumulated Clicks: 1
D/MBImageButton: Accumulated Clicks: 0
D/MBImageButton: Accumulated Clicks: 0
D/MBImageButton: Accumulated Clicks: 0
в оба раза я нажал с тем же интервалом времени, но когда эта кнопка добавлена в RecyclerView, это дает странный результат.
в чем может быть проблема ?
обновление:
мой адаптер упрощенная реализация:
public class ProductRVAdapter extends BaseProductRVAdapter<ProductRVAdapter.ProductVH> {
private ProductAdapterListener mProductAdapterListener;
public ProductRVAdapter(List<Product> productList, ProductAdapterListener productAdapterListener) {
super(productList);
mProductAdapterListener = productAdapterListener;
}
@Override
public ProductVH onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false);
return new ProductVH(view);
}
@Override
public void onBindViewHolder(ProductVH holder, int position) {
holder.bind(productList.get(position), position);
holder.ib_decrease.setAccumulatedClickListeners(currentCount -> onProductAdapterDecreaseClicked.onClick(holder.ib_decrease),
count -> {
if (mProductAdapterListener != null) {
mProductAdapterListener.onProductAdapterProductChangeToWSListener(productList.get(position), position, -count);
}
});
holder.ib_add.setAccumulatedClickListeners(currentCount -> onProductAdapterAddToCartClicked.onClick(holder.ib_add),
count -> {
if (mProductAdapterListener != null) {
mProductAdapterListener.onProductAdapterProductChangeToWSListener(productList.get(position), position, count);
}
});
}
public interface ProductAdapterListener {
void onProductAdapterAddToCartClicked(Product product, int position);
void onProductAdapterProductChangeToWSListener(Product product, int position, int amountChanged);
void onProductAdapterDecreaseClicked(Product product, int position);
}
public static class ProductVH extends RecyclerView.ViewHolder {
@BindView(R.id.iv_productImage)
ImageView iv_producImage;
@BindView(R.id.tv_productName)
TextView tv_productName;
@BindView(R.id.tv_prodAmount)
MBTextView tv_prodAmount;
@BindView(R.id.ib_decrease)
MBImageButton ib_decrease;
@BindView(R.id.ib_add)
MBImageButton ib_add;
ProductVH(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void bind(Product product, int position) {
tv_productName.setText(product.getName());
tv_prodAmount.setText(product.getAmount());
ImageLoader.loadImage(iv_producImage.getContext(), iv_producImage, product.getImg(), R.drawable.ic_category_item);
ib_decrease.setTag(position);
ib_add.setTag(position);
}
}
}
1 ответов
я получил решение
основная проблема заключается в том, когда я прокручиваю RecyclerView Adapter
, vies отсоединяются от окна и функции onDetachedFromWindow
вызывается, в этой функции i unsubscribe (dispose)
от наблюдаемых.
решения пробовал:
1. Я добавил attachFunction
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (shouldDisposeOnDetachFromWindow) {
if (mDisposable != null) {
if (mDisposable.isDisposed()) {
initClickObservable();
subscribe();
}
}
}
}
2. Удалены вызовы notifyItemChanged (позиция)
обычно я обновляю представление после некоторой модификации с помощью notifyItemChange()
. Теперь я обновляю представление непосредственно с помощью setText или аналогичных функций.
теперь он работает правильно.