677 lines
28 KiB
Java
677 lines
28 KiB
Java
/*
|
|
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
|
*
|
|
* This file is part of RoboSwag library.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package ru.touchin.roboswag.components.adapters;
|
|
|
|
import android.os.Looper;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.v7.widget.RecyclerView;
|
|
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 ru.touchin.roboswag.components.utils.LifecycleBindable;
|
|
import ru.touchin.roboswag.components.utils.UiUtils;
|
|
import ru.touchin.roboswag.core.log.Lc;
|
|
import ru.touchin.roboswag.core.observables.collections.ObservableCollection;
|
|
import ru.touchin.roboswag.core.observables.collections.ObservableList;
|
|
import ru.touchin.roboswag.core.observables.collections.changes.Change;
|
|
import ru.touchin.roboswag.core.observables.collections.changes.ChangePayloadProducer;
|
|
import ru.touchin.roboswag.core.observables.collections.changes.CollectionChanges;
|
|
import ru.touchin.roboswag.core.observables.collections.changes.SameItemsPredicate;
|
|
import ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList;
|
|
import ru.touchin.roboswag.core.utils.Optional;
|
|
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
|
import rx.Observable;
|
|
import rx.functions.Action1;
|
|
import rx.functions.Action2;
|
|
import rx.functions.Action3;
|
|
import rx.subjects.BehaviorSubject;
|
|
|
|
/**
|
|
* Created by Gavriil Sitnikov on 20/11/2015.
|
|
* Adapter based on {@link ObservableCollection} and providing some useful features like:
|
|
* - 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}.
|
|
*
|
|
* @param <TItem> Type of items to bind to ViewHolders;
|
|
* @param <TItemViewHolder> Type of ViewHolders to show items.
|
|
*/
|
|
@SuppressWarnings({"unchecked", "PMD.TooManyMethods"})
|
|
//TooManyMethods: it's ok
|
|
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends BindableViewHolder>
|
|
extends RecyclerView.Adapter<BindableViewHolder> {
|
|
|
|
private static final int PRE_LOADING_COUNT = 20;
|
|
|
|
private static boolean inDebugMode;
|
|
|
|
/**
|
|
* Enables debugging features like checking concurrent delegates.
|
|
*/
|
|
public static void setInDebugMode() {
|
|
inDebugMode = true;
|
|
}
|
|
|
|
@NonNull
|
|
private final BehaviorSubject<Optional<ObservableCollection<TItem>>> observableCollectionSubject
|
|
= BehaviorSubject.create(new Optional<>(null));
|
|
@NonNull
|
|
private final BehaviorSubject<Boolean> moreAutoLoadingRequested = BehaviorSubject.create();
|
|
@NonNull
|
|
private final LifecycleBindable lifecycleBindable;
|
|
@Nullable
|
|
private Object onItemClickListener;
|
|
private int lastUpdatedChangeNumber = -1;
|
|
|
|
@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<? extends BindableViewHolder>> delegates = new ArrayList<>();
|
|
|
|
public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) {
|
|
super();
|
|
this.lifecycleBindable = lifecycleBindable;
|
|
lifecycleBindable.untilDestroy(innerCollection.observeChanges(), this::onItemsChanged);
|
|
lifecycleBindable.untilDestroy(observableCollectionSubject
|
|
.switchMap(optional -> {
|
|
final ObservableCollection<TItem> collection = optional.get();
|
|
return collection != null ? collection.observeItems() : Observable.just(Collections.emptyList());
|
|
}), innerCollection::set);
|
|
lifecycleBindable.untilDestroy(createMoreAutoLoadingObservable());
|
|
}
|
|
|
|
@NonNull
|
|
private Observable createMoreAutoLoadingObservable() {
|
|
return observableCollectionSubject
|
|
.switchMap(collectionOptional -> {
|
|
final ObservableCollection<TItem> collection = collectionOptional.get();
|
|
if (!(collection instanceof LoadingMoreList)) {
|
|
return Observable.empty();
|
|
}
|
|
return moreAutoLoadingRequested
|
|
.distinctUntilChanged()
|
|
.switchMap(requested -> {
|
|
if (!requested) {
|
|
return Observable.empty();
|
|
}
|
|
final int size = collection.size();
|
|
return ((LoadingMoreList<?, ?, ?>) collection)
|
|
.loadRange(size, size + PRE_LOADING_COUNT)
|
|
.onErrorResumeNext(Observable.empty())
|
|
.doOnCompleted(() -> moreAutoLoadingRequested.onNext(false));
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns if any change of source collection applied to adapter.
|
|
* It's important to not show some footers or headers before first change have applied.
|
|
*
|
|
* @return True id any change applied.
|
|
*/
|
|
public boolean isAnyChangeApplied() {
|
|
return anyChangeApplied;
|
|
}
|
|
|
|
@Override
|
|
public void onAttachedToRecyclerView(@NonNull final RecyclerView recyclerView) {
|
|
super.onAttachedToRecyclerView(recyclerView);
|
|
attachedRecyclerViews.add(recyclerView);
|
|
}
|
|
|
|
private boolean anyRecyclerViewShown() {
|
|
for (final RecyclerView recyclerView : attachedRecyclerViews) {
|
|
if (recyclerView.isShown()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromRecyclerView(@NonNull final RecyclerView recyclerView) {
|
|
super.onDetachedFromRecyclerView(recyclerView);
|
|
attachedRecyclerViews.remove(recyclerView);
|
|
}
|
|
|
|
/**
|
|
* Returns parent {@link LifecycleBindable} (Activity/ViewController etc.).
|
|
*
|
|
* @return Parent {@link LifecycleBindable}.
|
|
*/
|
|
@NonNull
|
|
public LifecycleBindable getLifecycleBindable() {
|
|
return lifecycleBindable;
|
|
}
|
|
|
|
/**
|
|
* Returns {@link ObservableCollection} which provides items and it's changes.
|
|
*
|
|
* @return Inner {@link ObservableCollection}.
|
|
*/
|
|
@Nullable
|
|
public ObservableCollection<TItem> getObservableCollection() {
|
|
return observableCollectionSubject.getValue().get();
|
|
}
|
|
|
|
/**
|
|
* Method to observe {@link ObservableCollection} which provides items and it's changes.
|
|
*
|
|
* @return Observable of inner {@link ObservableCollection}.
|
|
*/
|
|
@NonNull
|
|
public Observable<Optional<ObservableCollection<TItem>>> observeObservableCollection() {
|
|
return observableCollectionSubject;
|
|
}
|
|
|
|
/**
|
|
* Sets {@link ObservableCollection} which will provide items and it's changes.
|
|
*
|
|
* @param observableCollection Inner {@link ObservableCollection}.
|
|
*/
|
|
public void setObservableCollection(@Nullable final ObservableCollection<TItem> observableCollection) {
|
|
this.observableCollectionSubject.onNext(new Optional<>(observableCollection));
|
|
}
|
|
|
|
/**
|
|
* Simply sets items.
|
|
*
|
|
* @param items Items to set.
|
|
*/
|
|
public void setItems(@NonNull final Collection<TItem> items) {
|
|
setObservableCollection(new ObservableList<>(items));
|
|
}
|
|
|
|
/**
|
|
* Calls when collection changes.
|
|
*
|
|
* @param collectionChanges Changes of collection.
|
|
*/
|
|
protected void onItemsChanged(@NonNull final CollectionChanges<TItem> collectionChanges) {
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
Lc.assertion("Items changes called on not main thread");
|
|
return;
|
|
}
|
|
if (!anyChangeApplied || !anyRecyclerViewShown()) {
|
|
anyChangeApplied = true;
|
|
refreshUpdate();
|
|
return;
|
|
}
|
|
if (collectionChanges.getNumber() != innerCollection.getChangesCount()
|
|
|| collectionChanges.getNumber() != lastUpdatedChangeNumber + 1) {
|
|
if (lastUpdatedChangeNumber < collectionChanges.getNumber()) {
|
|
refreshUpdate();
|
|
}
|
|
return;
|
|
}
|
|
notifyAboutChanges(collectionChanges.getChanges());
|
|
lastUpdatedChangeNumber = innerCollection.getChangesCount();
|
|
}
|
|
|
|
private void refreshUpdate() {
|
|
notifyDataSetChanged();
|
|
lastUpdatedChangeNumber = innerCollection.getChangesCount();
|
|
}
|
|
|
|
private void notifyAboutChanges(@NonNull final Collection<Change> changes) {
|
|
for (final Change change : changes) {
|
|
if (change instanceof Change.Inserted) {
|
|
final Change.Inserted castedChange = (Change.Inserted) change;
|
|
notifyItemRangeInserted(castedChange.getPosition() + getHeadersCount(), castedChange.getCount());
|
|
} else if (change instanceof Change.Removed) {
|
|
if (getItemCount() - getHeadersCount() == 0) {
|
|
//TODO: bug of recyclerview?
|
|
notifyDataSetChanged();
|
|
} else {
|
|
final Change.Removed castedChange = (Change.Removed) change;
|
|
notifyItemRangeRemoved(castedChange.getPosition() + getHeadersCount(), castedChange.getCount());
|
|
}
|
|
} else if (change instanceof Change.Moved) {
|
|
final Change.Moved castedChange = (Change.Moved) change;
|
|
notifyItemMoved(castedChange.getFromPosition() + getHeadersCount(), castedChange.getToPosition() + getHeadersCount());
|
|
} else if (change instanceof Change.Changed) {
|
|
final Change.Changed castedChange = (Change.Changed) change;
|
|
notifyItemRangeChanged(
|
|
castedChange.getPosition() + getHeadersCount(),
|
|
castedChange.getCount(),
|
|
castedChange.getPayload());
|
|
} else {
|
|
Lc.assertion("Not supported " + change);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns headers count goes before items.
|
|
*
|
|
* @return Headers count.
|
|
*/
|
|
protected int getHeadersCount() {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns footers count goes after items and headers.
|
|
*
|
|
* @return Footers count.
|
|
*/
|
|
protected int getFootersCount() {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns list of added delegates.
|
|
*
|
|
* @return List of {@link AdapterDelegate}.
|
|
*/
|
|
@NonNull
|
|
public List<AdapterDelegate<? extends BindableViewHolder>> getDelegates() {
|
|
return Collections.unmodifiableList(delegates);
|
|
}
|
|
|
|
/**
|
|
* Adds {@link ItemAdapterDelegate} to adapter.
|
|
*
|
|
* @param delegate Delegate to add.
|
|
*/
|
|
public void addDelegate(@NonNull final ItemAdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
|
|
addDelegateInternal(delegate);
|
|
}
|
|
|
|
/**
|
|
* Adds {@link PositionAdapterDelegate} to adapter.
|
|
*
|
|
* @param delegate Delegate to add.
|
|
*/
|
|
public void addDelegate(@NonNull final PositionAdapterDelegate<? extends BindableViewHolder> delegate) {
|
|
addDelegateInternal(delegate);
|
|
}
|
|
|
|
private void addDelegateInternal(@NonNull final AdapterDelegate<? extends BindableViewHolder> delegate) {
|
|
if (inDebugMode) {
|
|
for (final AdapterDelegate addedDelegate : delegates) {
|
|
if (addedDelegate.getItemViewType() == delegate.getItemViewType()) {
|
|
Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
delegates.add(delegate);
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
/**
|
|
* Removes {@link AdapterDelegate} from adapter.
|
|
*
|
|
* @param delegate Delegate to remove.
|
|
*/
|
|
public void removeDelegate(@NonNull final AdapterDelegate<? extends BindableViewHolder> delegate) {
|
|
delegates.remove(delegate);
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
private void checkDelegates(@Nullable final AdapterDelegate alreadyPickedDelegate, @NonNull final AdapterDelegate currentDelegate) {
|
|
if (alreadyPickedDelegate != null) {
|
|
throw new ShouldNotHappenException("Concurrent delegates: " + currentDelegate + " and " + alreadyPickedDelegate);
|
|
}
|
|
}
|
|
|
|
private int getItemPositionInCollection(final int positionInAdapter) {
|
|
final int shiftedPosition = positionInAdapter - getHeadersCount();
|
|
return shiftedPosition >= 0 && shiftedPosition < innerCollection.size() ? shiftedPosition : -1;
|
|
}
|
|
|
|
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity", "PMD.NPathComplexity"})
|
|
//Complexity: because of debug code
|
|
@Override
|
|
public int getItemViewType(final int positionInAdapter) {
|
|
AdapterDelegate delegateOfViewType = null;
|
|
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
|
|
final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
|
|
for (final AdapterDelegate<?> delegate : delegates) {
|
|
if (delegate instanceof ItemAdapterDelegate) {
|
|
if (item != null && ((ItemAdapterDelegate) delegate).isForViewType(item, positionInAdapter, positionInCollection)) {
|
|
checkDelegates(delegateOfViewType, delegate);
|
|
delegateOfViewType = delegate;
|
|
if (!inDebugMode) {
|
|
break;
|
|
}
|
|
}
|
|
} else if (delegate instanceof PositionAdapterDelegate) {
|
|
if (((PositionAdapterDelegate) delegate).isForViewType(positionInAdapter)) {
|
|
checkDelegates(delegateOfViewType, delegate);
|
|
delegateOfViewType = delegate;
|
|
if (!inDebugMode) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
Lc.assertion("Delegate of type " + delegate.getClass());
|
|
}
|
|
}
|
|
|
|
return delegateOfViewType != null ? delegateOfViewType.getItemViewType() : super.getItemViewType(positionInAdapter);
|
|
}
|
|
|
|
@Override
|
|
public long getItemId(final int positionInAdapter) {
|
|
final LongContainer result = new LongContainer();
|
|
tryDelegateAction(positionInAdapter,
|
|
(itemAdapterDelegate, item, positionInCollection) ->
|
|
result.value = itemAdapterDelegate.getItemId(item, positionInAdapter, positionInCollection),
|
|
positionAdapterDelegate -> result.value = positionAdapterDelegate.getItemId(positionInAdapter),
|
|
(item, positionInCollection) -> result.value = super.getItemId(positionInAdapter));
|
|
return result.value;
|
|
}
|
|
|
|
private void tryDelegateAction(final int positionInAdapter,
|
|
@NonNull final Action3<ItemAdapterDelegate, TItem, Integer> itemAdapterDelegateAction,
|
|
@NonNull final Action1<PositionAdapterDelegate> positionAdapterDelegateAction,
|
|
@NonNull final Action2<TItem, Integer> defaultAction) {
|
|
final int viewType = getItemViewType(positionInAdapter);
|
|
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
|
|
final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
|
|
for (final AdapterDelegate<?> delegate : delegates) {
|
|
if (delegate instanceof ItemAdapterDelegate) {
|
|
if (item != null && viewType == delegate.getItemViewType()) {
|
|
itemAdapterDelegateAction.call((ItemAdapterDelegate) delegate, item, positionInCollection);
|
|
return;
|
|
}
|
|
} else if (delegate instanceof PositionAdapterDelegate) {
|
|
if (viewType == delegate.getItemViewType()) {
|
|
positionAdapterDelegateAction.call((PositionAdapterDelegate) delegate);
|
|
return;
|
|
}
|
|
} else {
|
|
Lc.assertion("Delegate of type " + delegate.getClass());
|
|
}
|
|
}
|
|
defaultAction.call(item, positionInCollection);
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return getHeadersCount() + innerCollection.size() + getFootersCount();
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
|
for (final AdapterDelegate<?> delegate : delegates) {
|
|
if (delegate.getItemViewType() == viewType) {
|
|
return delegate.onCreateViewHolder(parent);
|
|
}
|
|
}
|
|
throw new ShouldNotHappenException("Add some AdapterDelegate or override this method");
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter) {
|
|
lastUpdatedChangeNumber = innerCollection.getChangesCount();
|
|
|
|
tryDelegateAction(positionInAdapter,
|
|
(itemAdapterDelegate, item, positionInCollection) -> {
|
|
bindItemViewHolder(itemAdapterDelegate, holder, item, null, positionInAdapter, positionInCollection);
|
|
updateMoreAutoLoadingRequest(positionInCollection);
|
|
},
|
|
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
|
|
(item, positionInCollection) -> {
|
|
if (item != null) {
|
|
bindItemViewHolder(null, holder, item, null, positionInAdapter, positionInCollection);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List<Object> payloads) {
|
|
super.onBindViewHolder(holder, positionInAdapter, payloads);
|
|
tryDelegateAction(positionInAdapter,
|
|
(itemAdapterDelegate, item, positionInCollection) -> {
|
|
bindItemViewHolder(itemAdapterDelegate, holder, item, payloads, positionInAdapter, positionInCollection);
|
|
updateMoreAutoLoadingRequest(positionInCollection);
|
|
},
|
|
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
|
|
(item, positionInCollection) -> {
|
|
if (item != null) {
|
|
bindItemViewHolder(null, holder, item, payloads, positionInAdapter, positionInCollection);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void bindItemViewHolder(@Nullable final ItemAdapterDelegate<TItemViewHolder, TItem> itemAdapterDelegate,
|
|
@NonNull final BindableViewHolder holder, @NonNull final TItem item, @Nullable final List<Object> payloads,
|
|
final int positionInAdapter, final int positionInCollection) {
|
|
final TItemViewHolder itemViewHolder;
|
|
try {
|
|
itemViewHolder = (TItemViewHolder) holder;
|
|
} catch (final ClassCastException exception) {
|
|
Lc.assertion(exception);
|
|
return;
|
|
}
|
|
updateClickListener(holder, item, positionInAdapter, positionInCollection);
|
|
if (itemAdapterDelegate != null) {
|
|
if (payloads == null) {
|
|
itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection);
|
|
} else {
|
|
itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection);
|
|
}
|
|
} else {
|
|
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()}.
|
|
* You don't need to override this method if you have delegates for every view type.
|
|
*
|
|
* @param holder ViewHolder to bind item to;
|
|
* @param positionInAdapter Position of ViewHolder (NOT item!);
|
|
* @param item Item returned by position (WITH HEADER OFFSET!).
|
|
*/
|
|
protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int positionInAdapter, @NonNull final TItem item) {
|
|
// do nothing by default - let delegates do it
|
|
}
|
|
|
|
/**
|
|
* 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 - let delegates do it
|
|
}
|
|
|
|
@Nullable
|
|
public TItem getItem(final int positionInAdapter) {
|
|
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
|
|
return positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
|
|
}
|
|
|
|
/**
|
|
* Sets item click listener.
|
|
*
|
|
* @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();
|
|
}
|
|
|
|
/**
|
|
* Sets item click listener.
|
|
*
|
|
* @param onItemClickListener Item click listener.
|
|
*/
|
|
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 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, final int positionInAdapter, final int positionInCollection) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Enable diff utils algorithm in collection changes.
|
|
*
|
|
* @param detectMoves The flag that determines whether the {@link Change.Moved} changes will be generated or not;
|
|
* @param sameItemsPredicate Predicate for the determination of the same elements;
|
|
* @param changePayloadProducer Function that calculate change payload when items the same but contents are different.
|
|
*/
|
|
public void enableDiffUtils(final boolean detectMoves,
|
|
@NonNull final SameItemsPredicate<TItem> sameItemsPredicate,
|
|
@Nullable final ChangePayloadProducer<TItem> changePayloadProducer) {
|
|
innerCollection.enableDiffUtils(detectMoves, sameItemsPredicate, changePayloadProducer);
|
|
}
|
|
|
|
/**
|
|
* Disable diff utils algorithm.
|
|
*/
|
|
public void disableDiffUtils() {
|
|
innerCollection.disableDiffUtils();
|
|
}
|
|
|
|
/**
|
|
* Returns enabled flag of diff utils.
|
|
*
|
|
* @return true if diff utils is enabled.
|
|
*/
|
|
public boolean diffUtilsIsEnabled() {
|
|
return innerCollection.diffUtilsIsEnabled();
|
|
}
|
|
|
|
/**
|
|
* Interface to simply add item click listener.
|
|
*
|
|
* @param <TItem> Type of item
|
|
*/
|
|
public interface OnItemClickListener<TItem> {
|
|
|
|
/**
|
|
* Calls when item have clicked.
|
|
*
|
|
* @param item Clicked item.
|
|
*/
|
|
void onItemClicked(@NonNull TItem item);
|
|
|
|
}
|
|
|
|
/**
|
|
* Interface to simply add item click listener based on item position in adapter and collection.
|
|
*
|
|
* @param <TItem> Type of item
|
|
*/
|
|
public interface OnItemWithPositionClickListener<TItem> {
|
|
|
|
/**
|
|
* Calls when item have clicked.
|
|
*
|
|
* @param item Clicked item;
|
|
* @param positionInAdapter Position of clicked item in adapter (with headers);
|
|
* @param positionInCollection Position of clicked item in inner collection.
|
|
*/
|
|
void onItemClicked(@NonNull TItem item, final int positionInAdapter, final int positionInCollection);
|
|
|
|
}
|
|
|
|
private class LongContainer {
|
|
|
|
private long value;
|
|
|
|
}
|
|
|
|
}
|