Merge branch 'feature/general_improvements' into master-rx-java-2
# Conflicts: # src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java # src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java # src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java # src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java # src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java
This commit is contained in:
commit
a4a0e0d4b8
|
|
@ -0,0 +1,253 @@
|
|||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.LifecycleBindable;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import rx.Completable;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
import rx.Subscription;
|
||||
import rx.functions.Action0;
|
||||
import rx.functions.Action1;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate;
|
||||
* @param <TItem> Type of items to bind to {@link BindableViewHolder}s.
|
||||
*/
|
||||
@SuppressWarnings("PMD.TooManyMethods")
|
||||
//TooManyMethods: it's ok as it is LifecycleBindable
|
||||
public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder, TItem> implements LifecycleBindable {
|
||||
|
||||
@NonNull
|
||||
private final LifecycleBindable parentLifecycleBindable;
|
||||
private final int defaultItemViewType;
|
||||
|
||||
public AdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
|
||||
this.parentLifecycleBindable = parentLifecycleBindable;
|
||||
this.defaultItemViewType = UiUtils.OfViews.generateViewId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent {@link LifecycleBindable} that this delegate created from (e.g. Activity or ViewController).
|
||||
*
|
||||
* @return Parent {@link LifecycleBindable}.
|
||||
*/
|
||||
@NonNull
|
||||
public LifecycleBindable getParentLifecycleBindable() {
|
||||
return parentLifecycleBindable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique ID of AdapterDelegate.
|
||||
*
|
||||
* @return Unique ID.
|
||||
*/
|
||||
public int getItemViewType() {
|
||||
return defaultItemViewType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}.
|
||||
*
|
||||
* @param item Item to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param itemCollectionPosition Position of item in collection that contains item;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public abstract boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int itemCollectionPosition);
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param item Item to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param itemCollectionPosition Position of item in collection that contains item;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int itemCollectionPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ViewHolder to bind item to it later.
|
||||
*
|
||||
* @param parent Container of ViewHolder's view.
|
||||
* @return New ViewHolder.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
|
||||
|
||||
/**
|
||||
* Binds item to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param item Item to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param itemCollectionPosition Position of item in collection that contains item;
|
||||
*/
|
||||
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item,
|
||||
final int adapterPosition, final int itemCollectionPosition);
|
||||
|
||||
/**
|
||||
* Binds item with payloads to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param item Item to check;
|
||||
* @param payloads Payloads;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param itemCollectionPosition Position of item in collection that contains item;
|
||||
*/
|
||||
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List<Object> payloads,
|
||||
final int adapterPosition, final int itemCollectionPosition) {
|
||||
//do nothing by default
|
||||
}
|
||||
|
||||
@SuppressWarnings("CPD-START")
|
||||
//CPD: it is same as in other implementation based on BaseLifecycleBindable
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Observable<T> observable) {
|
||||
return parentLifecycleBindable.untilStop(observable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
|
||||
return parentLifecycleBindable.untilStop(observable, onNextAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction,
|
||||
@NonNull final Action0 onCompletedAction) {
|
||||
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Single<T> single) {
|
||||
return parentLifecycleBindable.untilStop(single);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
|
||||
return parentLifecycleBindable.untilStop(single, onSuccessAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilStop(@NonNull final Single<T> single,
|
||||
@NonNull final Action1<T> onSuccessAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilStop(@NonNull final Completable completable) {
|
||||
return parentLifecycleBindable.untilStop(completable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
|
||||
return parentLifecycleBindable.untilStop(completable, onCompletedAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilStop(@NonNull final Completable completable,
|
||||
@NonNull final Action0 onCompletedAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
|
||||
return parentLifecycleBindable.untilDestroy(observable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
|
||||
return parentLifecycleBindable.untilDestroy(observable, onNextAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction,
|
||||
@NonNull final Action0 onCompletedAction) {
|
||||
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
|
||||
return parentLifecycleBindable.untilDestroy(single);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
|
||||
return parentLifecycleBindable.untilDestroy(single, onSuccessAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
|
||||
@NonNull final Action1<T> onSuccessAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilDestroy(@NonNull final Completable completable) {
|
||||
return parentLifecycleBindable.untilDestroy(completable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
|
||||
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription untilDestroy(@NonNull final Completable completable,
|
||||
@NonNull final Action0 onCompletedAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,19 +22,17 @@ package ru.touchin.roboswag.components.adapters;
|
|||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import ru.touchin.roboswag.components.utils.LifecycleBindable;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
|
|
@ -44,11 +42,13 @@ import ru.touchin.roboswag.core.observables.collections.ObservableCollection;
|
|||
import ru.touchin.roboswag.core.observables.collections.ObservableList;
|
||||
import ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList;
|
||||
import ru.touchin.roboswag.core.utils.Optional;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 20/11/2015.
|
||||
* Adapter based on {@link ObservableCollection} and providing some useful features like:
|
||||
* - item-based binding by {@link #onBindItemToViewHolder(ViewHolder, int, Object)}} method;
|
||||
* - item-based binding method;
|
||||
* - delegates by {@link AdapterDelegate} over itemViewType logic;
|
||||
* - item click listener setup by {@link #setOnItemClickListener(OnItemClickListener)};
|
||||
* - allows to inform about footers/headers by overriding base create/bind methods and {@link #getHeadersCount()} plus {@link #getFootersCount()};
|
||||
* - by default it is pre-loading items for collections like {@link ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList}.
|
||||
|
|
@ -56,27 +56,31 @@ import ru.touchin.roboswag.core.utils.Optional;
|
|||
* @param <TItem> Type of items to bind to ViewHolders;
|
||||
* @param <TItemViewHolder> Type of ViewHolders to show items.
|
||||
*/
|
||||
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends ObservableCollectionAdapter.ViewHolder>
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends BindableViewHolder>
|
||||
extends RecyclerView.Adapter<BindableViewHolder> {
|
||||
|
||||
private static final int PRE_LOADING_COUNT = 10;
|
||||
private static final int PRE_LOADING_COUNT = 20;
|
||||
|
||||
@NonNull
|
||||
private final BehaviorSubject<Optional<ObservableCollection<TItem>>> observableCollectionSubject
|
||||
= BehaviorSubject.createDefault(new Optional<>(null));
|
||||
@NonNull
|
||||
private final BehaviorSubject<Boolean> moreAutoLoadingRequested = BehaviorSubject.create();
|
||||
@NonNull
|
||||
private final LifecycleBindable lifecycleBindable;
|
||||
@Nullable
|
||||
private OnItemClickListener<TItem> onItemClickListener;
|
||||
private Object onItemClickListener;
|
||||
private int lastUpdatedChangeNumber = -1;
|
||||
@NonNull
|
||||
private final Observable historyPreLoadingObservable;
|
||||
|
||||
@NonNull
|
||||
private final ObservableList<TItem> innerCollection = new ObservableList<>();
|
||||
private boolean anyChangeApplied;
|
||||
private long itemClickDelayMillis;
|
||||
@NonNull
|
||||
private final List<RecyclerView> attachedRecyclerViews = new LinkedList<>();
|
||||
@NonNull
|
||||
private final List<AdapterDelegate<TItemViewHolder, TItem>> delegates = new ArrayList<>();
|
||||
|
||||
public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) {
|
||||
super();
|
||||
|
|
@ -89,16 +93,33 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
innerCollection.clear();
|
||||
return Observable.empty();
|
||||
}
|
||||
innerCollection.set(collection.getItems());
|
||||
return collection.observeChanges().observeOn(AndroidSchedulers.mainThread());
|
||||
}), this::onApplyChanges);
|
||||
historyPreLoadingObservable = observableCollectionSubject
|
||||
.switchMap(optional -> {
|
||||
final ObservableCollection<TItem> collection = optional.get();
|
||||
lifecycleBindable.untilDestroy(createMoreAutoLoadingObservable());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable createMoreAutoLoadingObservable() {
|
||||
return observableCollectionSubject
|
||||
.switchMap(collectionOptional -> {
|
||||
final ObservableCollection<TItem> collection = collectionOptional.get();
|
||||
if (!(collection instanceof LoadingMoreList)) {
|
||||
return Observable.empty();
|
||||
}
|
||||
final int size = collection.size();
|
||||
return ((LoadingMoreList) collection).loadRange(size, size + PRE_LOADING_COUNT).toObservable();
|
||||
return moreAutoLoadingRequested
|
||||
.distinctUntilChanged()
|
||||
.switchMap(requested -> {
|
||||
if (!requested) {
|
||||
return Observable.empty();
|
||||
}
|
||||
final int size = collection.size();
|
||||
return ((LoadingMoreList<?, ?, ?>) collection)
|
||||
.loadRange(size, size + PRE_LOADING_COUNT)
|
||||
.onErrorReturnItem(new ArrayList<>())
|
||||
.toObservable()
|
||||
.doOnComplete(() -> moreAutoLoadingRequested.onNext(false));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +145,7 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
|
||||
/**
|
||||
* Returns if any change of source collection applied to adapter.
|
||||
* It's important to not show some footers or header before first change have applied.
|
||||
* It's important to not show some footers or headers before first change have applied.
|
||||
*
|
||||
* @return True id any change applied.
|
||||
*/
|
||||
|
|
@ -275,6 +296,74 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of added delegates.
|
||||
*
|
||||
* @return List of {@link AdapterDelegate}.
|
||||
*/
|
||||
@NonNull
|
||||
public List<AdapterDelegate<TItemViewHolder, TItem>> getDelegates() {
|
||||
return Collections.unmodifiableList(delegates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link AdapterDelegate} to adapter.
|
||||
*
|
||||
* @param delegate Delegate to add.
|
||||
*/
|
||||
public void addDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
|
||||
for (final AdapterDelegate addedDelegate : delegates) {
|
||||
if (addedDelegate.getItemViewType() == delegate.getItemViewType()) {
|
||||
Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added");
|
||||
return;
|
||||
}
|
||||
}
|
||||
delegates.add((AdapterDelegate<TItemViewHolder, TItem>) delegate);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes {@link AdapterDelegate} from adapter.
|
||||
*
|
||||
* @param delegate Delegate to remove.
|
||||
*/
|
||||
public void removeDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
|
||||
delegates.remove((AdapterDelegate<TItemViewHolder, TItem>) delegate);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(final int positionInAdapter) {
|
||||
final int positionInCollection = positionInAdapter - getHeadersCount();
|
||||
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
|
||||
return super.getItemViewType(positionInAdapter);
|
||||
}
|
||||
final TItem item = innerCollection.get(positionInCollection);
|
||||
for (final AdapterDelegate<?, TItem> delegate : delegates) {
|
||||
if (delegate.isForViewType(item, positionInAdapter, positionInCollection)) {
|
||||
return delegate.getItemViewType();
|
||||
}
|
||||
}
|
||||
return super.getItemViewType(positionInAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int positionInAdapter) {
|
||||
final int positionInCollection = positionInAdapter - getHeadersCount();
|
||||
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
|
||||
return super.getItemId(positionInAdapter);
|
||||
}
|
||||
|
||||
final int itemViewType = getItemViewType(positionInAdapter);
|
||||
final TItem item = innerCollection.get(positionInCollection);
|
||||
for (final AdapterDelegate<?, TItem> delegate : delegates) {
|
||||
if (delegate.getItemViewType() == itemViewType) {
|
||||
return delegate.getItemId(item, positionInAdapter, positionInCollection);
|
||||
}
|
||||
}
|
||||
return super.getItemId(positionInAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return getHeadersCount() + innerCollection.size() + getFootersCount();
|
||||
|
|
@ -282,18 +371,40 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
|
||||
@NonNull
|
||||
@Override
|
||||
public abstract BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType);
|
||||
public BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
for (final AdapterDelegate<?, TItem> delegate : delegates) {
|
||||
if (delegate.getItemViewType() == viewType) {
|
||||
return delegate.onCreateViewHolder(parent);
|
||||
}
|
||||
}
|
||||
throw new ShouldNotHappenException("Add some AdapterDelegate or override this method");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int position) {
|
||||
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter) {
|
||||
lastUpdatedChangeNumber = innerCollection.getChangesCount();
|
||||
|
||||
final int itemPosition = position - getHeadersCount();
|
||||
if (itemPosition < 0 || itemPosition >= innerCollection.size()) {
|
||||
final int positionInCollection = positionInAdapter - getHeadersCount();
|
||||
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateMoreAutoLoadingRequest(positionInCollection);
|
||||
bindItemViewHolder(holder, null, positionInAdapter, positionInCollection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List<Object> payloads) {
|
||||
super.onBindViewHolder(holder, positionInAdapter, payloads);
|
||||
final int positionInCollection = positionInAdapter - getHeadersCount();
|
||||
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
|
||||
return;
|
||||
}
|
||||
bindItemViewHolder(holder, payloads, positionInAdapter, positionInCollection);
|
||||
}
|
||||
|
||||
private void bindItemViewHolder(@NonNull final BindableViewHolder holder, @Nullable final List<Object> payloads,
|
||||
final int positionInAdapter, final int positionInCollection) {
|
||||
final TItemViewHolder itemViewHolder;
|
||||
try {
|
||||
itemViewHolder = (TItemViewHolder) holder;
|
||||
|
|
@ -301,29 +412,79 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
Lc.assertion(exception);
|
||||
return;
|
||||
}
|
||||
final TItem item = innerCollection.get(itemPosition);
|
||||
itemViewHolder.setAdapter(this);
|
||||
onBindItemToViewHolder(itemViewHolder, position, item);
|
||||
itemViewHolder.bindPosition(position);
|
||||
if (onItemClickListener != null && !isOnClickListenerDisabled(item)) {
|
||||
UiUtils.setOnRippleClickListener(holder.itemView, () -> onItemClickListener.onItemClicked(item, position), getItemClickDelay());
|
||||
final TItem item = innerCollection.get(positionInCollection);
|
||||
final int itemViewType = getItemViewType(positionInAdapter);
|
||||
|
||||
updateClickListener(holder, item, positionInAdapter, positionInCollection);
|
||||
for (final AdapterDelegate<TItemViewHolder, TItem> delegate : delegates) {
|
||||
if (itemViewType == delegate.getItemViewType()) {
|
||||
if (payloads == null) {
|
||||
delegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection);
|
||||
} else {
|
||||
delegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (payloads == null) {
|
||||
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item);
|
||||
} else {
|
||||
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateClickListener(@NonNull final BindableViewHolder holder, @NonNull final TItem item,
|
||||
final int positionInAdapter, final int positionInCollection) {
|
||||
if (onItemClickListener != null && !isOnClickListenerDisabled(item, positionInAdapter, positionInCollection)) {
|
||||
UiUtils.setOnRippleClickListener(holder.itemView,
|
||||
() -> {
|
||||
if (onItemClickListener instanceof OnItemClickListener) {
|
||||
((OnItemClickListener) onItemClickListener).onItemClicked(item);
|
||||
} else if (onItemClickListener instanceof OnItemWithPositionClickListener) {
|
||||
((OnItemWithPositionClickListener) onItemClickListener).onItemClicked(item, positionInAdapter, positionInCollection);
|
||||
} else {
|
||||
Lc.assertion("Unexpected onItemClickListener type " + onItemClickListener);
|
||||
}
|
||||
},
|
||||
itemClickDelayMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMoreAutoLoadingRequest(final int positionInCollection) {
|
||||
if (positionInCollection > innerCollection.size() - PRE_LOADING_COUNT) {
|
||||
return;
|
||||
}
|
||||
moreAutoLoadingRequested.onNext(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder.
|
||||
* It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param position Position of ViewHolder (NOT item!);
|
||||
* @param item Item returned by position (WITH HEADER OFFSET!).
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param positionInAdapter Position of ViewHolder (NOT item!);
|
||||
* @param item Item returned by position (WITH HEADER OFFSET!).
|
||||
*/
|
||||
protected abstract void onBindItemToViewHolder(@NonNull TItemViewHolder holder, int position, @NonNull TItem item);
|
||||
protected abstract void onBindItemToViewHolder(@NonNull TItemViewHolder holder, int positionInAdapter, @NonNull TItem item);
|
||||
|
||||
/**
|
||||
* Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder with payloads.
|
||||
* It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param positionInAdapter Position of ViewHolder in adapter (NOT item!);
|
||||
* @param item Item returned by position (WITH HEADER OFFSET!);
|
||||
* @param payloads Payloads.
|
||||
*/
|
||||
protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int positionInAdapter, @NonNull final TItem item,
|
||||
@NonNull final List<Object> payloads) {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TItem getItem(final int position) {
|
||||
final int positionInList = position - getHeadersCount();
|
||||
return positionInList < 0 || positionInList >= innerCollection.size() ? null : innerCollection.get(positionInList);
|
||||
public TItem getItem(final int positionInAdapter) {
|
||||
final int positionInCollection = positionInAdapter - getHeadersCount();
|
||||
return positionInCollection < 0 || positionInCollection >= innerCollection.size() ? null : innerCollection.get(positionInCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -332,26 +493,51 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
* @param onItemClickListener Item click listener.
|
||||
*/
|
||||
public void setOnItemClickListener(@Nullable final OnItemClickListener<TItem> onItemClickListener) {
|
||||
this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets item click listener.
|
||||
*
|
||||
* @param onItemClickListener Item click listener;
|
||||
* @param itemClickDelayMillis Delay of calling click listener.
|
||||
*/
|
||||
public void setOnItemClickListener(@Nullable final OnItemClickListener<TItem> onItemClickListener, final long itemClickDelayMillis) {
|
||||
this.onItemClickListener = onItemClickListener;
|
||||
this.itemClickDelayMillis = itemClickDelayMillis;
|
||||
refreshUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns delay of item click. By default returns delay of ripple effect duration.
|
||||
* Sets item click listener.
|
||||
*
|
||||
* @return Milliseconds delay of click.
|
||||
* @param onItemClickListener Item click listener.
|
||||
*/
|
||||
protected long getItemClickDelay() {
|
||||
return UiUtils.RIPPLE_EFFECT_DELAY;
|
||||
public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener<TItem> onItemClickListener) {
|
||||
this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets item click listener.
|
||||
*
|
||||
* @param onItemClickListener Item click listener;
|
||||
* @param itemClickDelayMillis Delay of calling click listener.
|
||||
*/
|
||||
public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener<TItem> onItemClickListener, final long itemClickDelayMillis) {
|
||||
this.onItemClickListener = onItemClickListener;
|
||||
this.itemClickDelayMillis = itemClickDelayMillis;
|
||||
refreshUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if click listening disabled or not for specific item.
|
||||
*
|
||||
* @param item Item to check click availability;
|
||||
* @param item Item to check click availability;
|
||||
* @param positionInAdapter Position of clicked item in adapter (with headers);
|
||||
* @param positionInCollection Position of clicked item in inner collection;
|
||||
* @return True if click listener enabled for such item.
|
||||
*/
|
||||
public boolean isOnClickListenerDisabled(@NonNull final TItem item) {
|
||||
public boolean isOnClickListenerDisabled(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -365,78 +551,27 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
|
|||
/**
|
||||
* Calls when item have clicked.
|
||||
*
|
||||
* @param item Clicked item;
|
||||
* @param position Position of clicked item.
|
||||
* @param item Clicked item.
|
||||
*/
|
||||
void onItemClicked(@NonNull TItem item, int position);
|
||||
void onItemClicked(@NonNull TItem item);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Base item ViewHolder that have included pre-loading logic.
|
||||
* Interface to simply add item click listener based on item position in adapter and collection.
|
||||
*
|
||||
* @param <TItem> Type of item
|
||||
*/
|
||||
public static class ViewHolder extends BindableViewHolder {
|
||||
|
||||
//it is needed to avoid massive requests on initial view holders attaching (like if we will add 10 items they all will try to load history)
|
||||
private static final long DELAY_BEFORE_LOADING_HISTORY = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
@Nullable
|
||||
private Disposable historyPreLoadingSubscription;
|
||||
@Nullable
|
||||
private ObservableCollectionAdapter adapter;
|
||||
|
||||
public ViewHolder(@NonNull final LifecycleBindable baseBindable, @NonNull final View itemView) {
|
||||
super(baseBindable, itemView);
|
||||
}
|
||||
public interface OnItemWithPositionClickListener<TItem> {
|
||||
|
||||
/**
|
||||
* Bind position to enable pre-loading for connected {@link ObservableCollection}.
|
||||
* Calls when item have clicked.
|
||||
*
|
||||
* @param position Position of ViewHolder.
|
||||
* @param item Clicked item;
|
||||
* @param positionInAdapter Position of clicked item in adapter (with headers);
|
||||
* @param positionInCollection Position of clicked item in inner collection.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
//unchecked: it's ok, we just need to load something more
|
||||
public void bindPosition(final int position) {
|
||||
if (historyPreLoadingSubscription != null) {
|
||||
historyPreLoadingSubscription.dispose();
|
||||
historyPreLoadingSubscription = null;
|
||||
}
|
||||
if (adapter != null && position - adapter.getHeadersCount() > adapter.innerCollection.size() - PRE_LOADING_COUNT) {
|
||||
historyPreLoadingSubscription = untilDestroy(adapter.historyPreLoadingObservable
|
||||
.delaySubscription(DELAY_BEFORE_LOADING_HISTORY, TimeUnit.MILLISECONDS)
|
||||
.onErrorResumeNext(Observable.empty()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.DefaultPackage")
|
||||
//it is for internal use only
|
||||
@Deprecated
|
||||
void setAdapter(@Nullable final ObservableCollectionAdapter adapter) {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply get String from resources.
|
||||
*
|
||||
* @param stringRes string resource id;
|
||||
* @return Requested String that matches with provided string resource id.
|
||||
*/
|
||||
@NonNull
|
||||
public String getString(@StringRes final int stringRes) {
|
||||
return itemView.getContext().getString(stringRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply get String from resources with arguments.
|
||||
*
|
||||
* @param stringRes String resource id;
|
||||
* @param formatArgs The format arguments that will be used for substitution;
|
||||
* @return Requested String that matches with provided string resource id.
|
||||
*/
|
||||
@NonNull
|
||||
public String getString(@StringRes final int stringRes, @Nullable final Object... formatArgs) {
|
||||
return itemView.getContext().getString(stringRes, formatArgs);
|
||||
}
|
||||
void onItemClicked(@NonNull TItem item, final int positionInAdapter, final int positionInCollection);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue