diff --git a/build.gradle b/build.gradle index f5f84d5..1575b38 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 24 - buildToolsVersion "24.0.1" + buildToolsVersion "24.0.2" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -19,8 +19,8 @@ android { dependencies { compile project(':libraries:core') - provided 'com.android.support:appcompat-v7:24.1.1' - compile 'com.android.support:recyclerview-v7:24.1.1' + provided 'com.android.support:appcompat-v7:24.2.0' + provided 'com.android.support:recyclerview-v7:24.2.0' provided 'io.reactivex:rxandroid:1.2.1' } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java index 7756014..bcdfe67 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java @@ -37,11 +37,14 @@ import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 12/8/2016. - * TODO: fill description + * ViewHolder that implements {@link LifecycleBindable} and uses parent bindable object as bridge (Activity, ViewController etc.). + * It is important to use such ViewHolder to avoid endless bindings when parent bindable have started but ViewHolder already detached from window. + * So inside method {@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)} + * use only inner {@link #bind(Observable, Action1)} method but not parent's bind method. */ public class BindableViewHolder extends RecyclerView.ViewHolder implements LifecycleBindable { - // it is needed to delay detach to avoid re-subscriptions on fast scroll + //HACK: it is needed to delay detach to avoid re-subscriptions on fast scroll private static final long DETACH_DELAY = TimeUnit.SECONDS.toMillis(1); @NonNull @@ -62,15 +65,26 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec .refCount(); } + /** + * Calls when {@link RecyclerView.ViewHolder} have attached to window. + */ @CallSuper public void onAttachedToWindow() { attachedToWindowSubject.onNext(true); } + /** + * Returns if {@link RecyclerView.ViewHolder} attached to window or not. + * + * @return True if {@link RecyclerView.ViewHolder} attached to window. + */ public boolean isAttachedToWindow() { return attachedToWindowSubject.hasValue() && attachedToWindowSubject.getValue(); } + /** + * Calls when {@link RecyclerView.ViewHolder} have detached from window. + */ @CallSuper public void onDetachedFromWindow() { attachedToWindowSubject.onNext(false); diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 18f2616..d0b0c17 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import ru.touchin.roboswag.components.R; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; @@ -45,15 +46,20 @@ import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 20/11/2015. - * TODO: fill description + * Adapter based on {@link ObservableCollection} and providing some useful features like: + * - item-based binding by {@link #onCreateItemViewHolder(ViewGroup, int)} and {@link #onBindItemToViewHolder(ViewHolder, int, Object)}} methods; + * - 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 Type of items to bind to ViewHolders; + * @param Type of ViewHolders to show items. */ public abstract class ObservableCollectionAdapter extends RecyclerView.Adapter { private static final int PRE_LOADING_COUNT = 10; - private static final int LIST_ITEM_TYPE = 12313212; - @NonNull private final BehaviorSubject> observableCollectionSubject = BehaviorSubject.create((ObservableCollection) null); @@ -112,53 +118,6 @@ public abstract class ObservableCollectionAdapter items) { - setObservableCollection(new ObservableList<>(items)); - } - - @Nullable - public ObservableCollection getObservableCollection() { - return observableCollectionSubject.getValue(); - } - - @NonNull - public Observable> observeObservableCollection() { - return observableCollectionSubject.distinctUntilChanged(); - } - - protected int getHeadersCount() { - return 0; - } - - protected int getFootersCount() { - return 0; - } - - private void refreshUpdate() { - notifyDataSetChanged(); - lastUpdatedChangeNumber = innerCollection.getChangesCount(); - } - - public void setObservableCollection(@Nullable final ObservableCollection observableCollection) { - this.observableCollectionSubject.onNext(observableCollection); - refreshUpdate(); - } - private boolean anyRecyclerViewShown() { for (final RecyclerView recyclerView : attachedRecyclerViews) { if (recyclerView.isShown()) { @@ -168,6 +127,66 @@ public abstract class ObservableCollectionAdapter getObservableCollection() { + return observableCollectionSubject.getValue(); + } + + /** + * Method to observe {@link ObservableCollection} which provides items and it's changes. + * + * @return Observable of inner {@link ObservableCollection}. + */ + @NonNull + public Observable> observeObservableCollection() { + return observableCollectionSubject.distinctUntilChanged(); + } + + /** + * Sets {@link ObservableCollection} which will provide items and it's changes. + * + * @param observableCollection Inner {@link ObservableCollection}. + */ + public void setObservableCollection(@Nullable final ObservableCollection observableCollection) { + this.observableCollectionSubject.onNext(observableCollection); + refreshUpdate(); + } + + /** + * Simply sets items. + * + * @param items Items to set. + */ + public void setItems(@NonNull final Collection items) { + setObservableCollection(new ObservableList<>(items)); + } + + /** + * Calls when collection changes. + * + * @param collectionChange Changes of collection. + */ protected void onItemsChanged(@NonNull final ObservableCollection.CollectionChange collectionChange) { if (Looper.myLooper() != Looper.getMainLooper()) { Lc.assertion("Items changes called on not main thread"); @@ -189,6 +208,11 @@ public abstract class ObservableCollectionAdapter> changes) { for (final Change change : changes) { switch (change.getType()) { @@ -213,14 +237,32 @@ public abstract class ObservableCollectionAdapter onItemClickListener) { - this.onItemClickListener = onItemClickListener; - refreshUpdate(); + /** + * 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; + } + + @Override + public int getItemCount() { + return getHeadersCount() + innerCollection.size() + getFootersCount(); } @Override public int getItemViewType(final int position) { - return LIST_ITEM_TYPE; + return R.id.OBSERVABLE_COLLECTION_ITEM_VIEW_TYPE; } @Override @@ -228,18 +270,25 @@ public abstract class ObservableCollectionAdapter= innerCollection.size()) { return; } - final TItem item = innerCollection.get(position - getHeadersCount()); + final TItem item = innerCollection.get(position - getHeadersCount()); onBindItemToViewHolder((TViewHolder) holder, position, item); ((TViewHolder) holder).bindPosition(position); if (onItemClickListener != null && !isOnClickListenerDisabled(item)) { @@ -247,6 +296,14 @@ public abstract class ObservableCollectionAdapter= innerCollection.size() ? null : innerCollection.get(positionInList); } - @Override - public int getItemCount() { - return getHeadersCount() + innerCollection.size() + getFootersCount(); - } - @Override public void onViewAttachedToWindow(@NonNull final BindableViewHolder holder) { super.onViewAttachedToWindow(holder); @@ -272,16 +324,55 @@ public abstract class ObservableCollectionAdapter onItemClickListener) { + this.onItemClickListener = onItemClickListener; + refreshUpdate(); + } + + /** + * Returns delay of item click. By default returns delay of ripple effect duration. + * + * @return Milliseconds delay of click. + */ + protected long getItemClickDelay() { + return UiUtils.RIPPLE_EFFECT_DELAY; + } + + /** + * Returns if click listening disabled or not for specific item. + * + * @param item Item to check click availability; + * @return True if click listener enabled for such item. + */ public boolean isOnClickListenerDisabled(@NonNull final TItem item) { return false; } + /** + * Interface to simply add item click listener. + * + * @param Type of item + */ public interface OnItemClickListener { + /** + * Calls when item have clicked. + * + * @param item Clicked item; + * @param position Position of clicked item. + */ void onItemClicked(@NonNull TItem item, int position); } + /** + * Base item ViewHolder that have included pre-loading logic. + */ public class ViewHolder extends BindableViewHolder { @Nullable @@ -291,7 +382,11 @@ public abstract class ObservableCollectionAdapter= Build.VERSION_CODES.LOLLIPOP ? 150 : 0; diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 9218041..3633e5b 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -19,4 +19,6 @@ + + \ No newline at end of file