package com.marshalchen.ultimaterecyclerview; import android.animation.Animator; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.os.Build; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.marshalchen.ultimaterecyclerview.itemTouchHelper.ItemTouchHelperAdapter; import com.marshalchen.ultimaterecyclerview.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * An abstract adapter which can be extended for Recyclerview */ public abstract class UltimateViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> implements StickyRecyclerHeadersAdapter<RecyclerView.ViewHolder>, ItemTouchHelperAdapter { protected UltimateRecyclerView.CustomRelativeWrapper customHeaderView = null; protected View customLoadMoreView = null; protected View customLoadMoreItemView = null; private boolean customHeader = false; /** * this watches how many times does this loading more triggered */ private int loadmoresetingswatch = 0; public boolean enabled_custom_load_more_view = false; protected int mEmptyViewPolicy; protected int mEmptyViewOnInitPolicy; /** * Set the header view of the adapter. * * @param customHeaderView na */ public void setCustomHeaderView(UltimateRecyclerView.CustomRelativeWrapper customHeaderView) { this.customHeaderView = customHeaderView; customHeader = true; } public UltimateRecyclerView.CustomRelativeWrapper getCustomHeaderView() { return customHeaderView; } public boolean hasHeaderView() { return customHeader; } /** * Using a custom LoadMoreView * * @param customview the inflated view */ public final void setCustomLoadMoreView(@Nullable View customview) { customLoadMoreView = customview; } public final View getCustomLoadMoreView() { return customLoadMoreView; } /** * the get function to get load more * * @return determine this is a get function */ public final boolean enableLoadMore() { return enabled_custom_load_more_view; } /** * as the set function to switching load more feature * * @param b bool */ public final void enableLoadMore(boolean b) { enabled_custom_load_more_view = b; if (!b && loadmoresetingswatch > 0 && customLoadMoreView != null) { notifyItemRemoved(getItemCount() - 1); } if (b && customLoadMoreView == null) { enabled_custom_load_more_view = false; } loadmoresetingswatch++; } public final void setEmptyViewPolicy(final int policy) { mEmptyViewPolicy = policy; } public final void setEmptyViewOnInitPolicy(final int policy) { mEmptyViewOnInitPolicy = policy; } public final int getEmptyViewPolicy() { return mEmptyViewPolicy; } public final int getEmptyViewInitPolicy() { return mEmptyViewOnInitPolicy; } /** * the basic view holder creation * * @param parent coming from the bottom api * @param viewType coming the bottom api as well * @return expected a typed view holder */ @Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPES.FOOTER) { VH viewHolder = getViewHolder(customLoadMoreView); /** * this is only for the first time rendering of the adapter */ customLoadMoreItemView = viewHolder.itemView; if (getAdapterItemCount() == 0) { customLoadMoreItemView.setVisibility(View.INVISIBLE); } return viewHolder; } else if (viewType == VIEW_TYPES.HEADER) { return getViewHolder(customHeaderView); } else if (viewType == VIEW_TYPES.ADVIEW) { return getAdViewHolder(customHeaderView); } else if (viewType == VIEW_TYPES.CUSTOMVIEW) { return getCustomViewHolder(customHeaderView); } else if (viewType == VIEW_TYPES.NOVIEW) { return getNoViewHolder(customHeaderView); } return onCreateViewHolder(parent); } /** * requirement: ADVIEW * * @param view v * @return holder for this ADVIEW */ public VH getAdViewHolder(View view) { return null; } /** * requirement: CUSTOMVIEW * * @param view v * @return v */ public VH getCustomViewHolder(View view) { return null; } /** * requirement: NOVIEW * * @param view v * @return v */ public VH getNoViewHolder(View view) { return null; } /** * requirement: FOOTER, HEADER. it does not bind and need to do that in the header binding * * @param view with no binding view of nothing * @return v */ public abstract VH getViewHolder(View view); /** * for all NORMAL type holder * * @param parent view group parent * @return vh */ public abstract VH onCreateViewHolder(ViewGroup parent); @Override public int getItemViewType(int position) { // int k = getAdapterItemCount(); if (getAdapterItemCount() == 0) { if (position == 0) { if (enableLoadMore() && hasHeaderView()) { //both return VIEW_TYPES.FOOTER; } else if (!enableLoadMore() && hasHeaderView()) { //only header return VIEW_TYPES.HEADER; } else if (enableLoadMore() && !hasHeaderView()) { //only load more return VIEW_TYPES.FOOTER; } else { return VIEW_TYPES.NOVIEW; } } else if (position == 1) { if (enableLoadMore() && hasHeaderView()) { //both return VIEW_TYPES.FOOTER; } else if (!enableLoadMore() && hasHeaderView()) { //only header return VIEW_TYPES.NOVIEW; } else if (enableLoadMore() && !hasHeaderView()) { //only load more return VIEW_TYPES.NOVIEW; } else { return VIEW_TYPES.NOVIEW; } } else { return VIEW_TYPES.NOVIEW; } } else if (getAdapterItemCount() > 0) { int last_item = getItemCount() - 1; if (position == last_item && enableLoadMore()) { return VIEW_TYPES.FOOTER; } else if (position == 0 && hasHeaderView()) { return VIEW_TYPES.HEADER; } else if (isOnCustomView(position)) { return VIEW_TYPES.ADVIEW; } else if (isOnAdView(position)) { return VIEW_TYPES.ADVIEW; } else { return VIEW_TYPES.NORMAL; } } else { return VIEW_TYPES.NORMAL; } } protected boolean isOnCustomView(final int pos) { return false; } protected boolean isOnAdView(final int pos) { return false; } /** * retrieve the amount of the total items in the urv for display that will be including all data items as well as the decorative items * * @return the int */ @Override public int getItemCount() { return getAdapterItemCount() + totalAdditionalItems(); } public int getAdditionalItems() { return totalAdditionalItems(); } protected int totalAdditionalItems() { int offset = 0; if (hasHeaderView()) offset++; if (enableLoadMore()) offset++; return offset; } /** * Returns the number of items in the adapter bound to the parent RecyclerView. * * @return The number of data items in the bound adapter */ public abstract int getAdapterItemCount(); public final void toggleSelection(int pos) { notifyItemChanged(pos); } public final void clearSelection(int pos) { notifyItemChanged(pos); } public final void setSelected(int pos) { notifyItemChanged(pos); } /** * Swap the item of list * * @param list data list * @param from position from * @param to position to */ public void swapPositions(List<?> list, int from, int to) { if (hasHeaderView()) { from--; to--; } if (enableLoadMore() && to == getItemCount() - 1) return; if (hasHeaderView() && to == 0) return; if (hasHeaderView() && from == 0) return; if (enableLoadMore() && from == getItemCount() - 1) return; Collections.swap(list, from, to); } /** * Insert a item to the list of the adapter * * @param list data list * @param object object T * @param position position * @param <T> in T */ public final <T> void insertInternal(List<T> list, T object, final int position) { list.add(position, object); int g = position; if (hasHeaderView()) g++; notifyItemInserted(g); } public final <T> void insertFirstInternal(List<T> list, T item) { insertInternal(list, item, 0); } public final <T> void insertLastInternal(List<T> list, T item) { insertInternal(list, item, getAdapterItemCount()); } /** * insert the new item list after the whole list * * @param insert_data new list * @param original_list original copy * @param <T> the type */ public final <T> void insertInternal(List<T> insert_data, List<T> original_list) { try { Iterator<T> id = insert_data.iterator(); int g = getItemCount(); // if (hasHeaderView()) g--; if (enableLoadMore()) g--; final int start = g; while (id.hasNext()) { original_list.add(original_list.size(), id.next()); } if (insert_data.size() == 1) { notifyItemInserted(start); } else if (insert_data.size() > 1) { notifyItemRangeInserted(start, insert_data.size()); } } catch (Exception e) { String o = e.fillInStackTrace().getCause().getMessage().toString(); Log.d("fillInStackTrace", o + " : "); } } /** * Remove a item of the list of the adapter * * @param list na * @param position na * @param <T> na */ public final <T> void removeInternal(List<T> list, int position) { if (hasHeaderView() && position == 0) return; if (enableLoadMore() && position == getItemCount() - 1) return; if (list.size() > 0) { list.remove(hasHeaderView() ? position - 1 : position); notifyItemRemoved(position); } } public final <T> void removeFirstInternal(List<T> list) { removeInternal(list, 0); } public final <T> void removeLastInternal(List<T> list) { removeInternal(list, getAdapterItemCount() - 1); } /** * Clear the list of the adapter * * @param list data list * @param <T> na */ public final <T> void clearInternal(List<T> list) { int data_size_before_remove = list.size(); final int display_size_before_remove = getItemCount(); list.clear(); notifyAfterRemoveAllData(data_size_before_remove, display_size_before_remove); } /** * @param data_size_before_remove data size * @param display_size_before_remove display item size * @return TRUE for this is done and no more further processing */ protected boolean detectDispatchLoadMoreDisplay(final int data_size_before_remove, final int display_size_before_remove) { if (data_size_before_remove == 0) { if (display_size_before_remove == 2) { if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER_AND_LOARMORE) { } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER) { removeDispatchLoadMoreView(); } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_CLEAR_ALL) { removeDispatchLoadMoreView(); } } else if (display_size_before_remove == 1) { if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER_AND_LOARMORE) { } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER) { removeDispatchLoadMoreView(); } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_CLEAR_ALL) { removeDispatchLoadMoreView(); } return true; } else if (display_size_before_remove == 0) { if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER_AND_LOARMORE) { notifyDataSetChanged(); } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER) { notifyDataSetChanged(); } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_SHOW_LOADMORE_ONLY) { notifyDataSetChanged(); } return true; } else { return false; } } return false; } protected void removeDispatchLoadMoreView() { if (customLoadMoreItemView != null) { customLoadMoreItemView.setVisibility(View.GONE); } } /** * works on API v23 * there is a high chance to crash this * * @param data_size_before_remove original size before removed * @param display_size_before_remove the counts for display items * <code> * http://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position</code> */ protected void notifyAfterRemoveAllData(final int data_size_before_remove, final int display_size_before_remove) { try { final int notify_start_item = hasHeaderView() ? 1 : 0; if (detectDispatchLoadMoreDisplay(data_size_before_remove, display_size_before_remove)) return; if (data_size_before_remove == 0) return; if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER_AND_LOARMORE) { if (hasHeaderView()) notifyItemRangeChanged(notify_start_item, data_size_before_remove); else { notifyDataSetChanged(); } } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_KEEP_HEADER) { notifyItemRangeRemoved(notify_start_item, data_size_before_remove); removeDispatchLoadMoreView(); } else if (mEmptyViewPolicy == UltimateRecyclerView.EMPTY_CLEAR_ALL) { notifyItemRangeRemoved(0, data_size_before_remove); removeDispatchLoadMoreView(); } else { notifyItemRangeRemoved(0, data_size_before_remove); } } catch (Exception e) { String o = e.fillInStackTrace().getCause().getMessage().toString(); Log.d("fillInStackTrace", o + " : "); } } /** * remove all items * * @param list na * @param <T> na */ public final <T> void removeAllInternal(List<T> list) { clearInternal(list); } @Override public long getHeaderId(int position) { if (hasHeaderView() && position == 0) return -1; if (enableLoadMore() && position >= getItemCount() - 1) return -1; if (getAdapterItemCount() > 0) { return generateHeaderId(hasHeaderView() ? position - 1 : position); } else return -1; } public abstract long generateHeaderId(int position); public static class VIEW_TYPES { public static final int NORMAL = 0; public static final int HEADER = 1; //this is the default loading footer public static final int FOOTER = 2; //this is the customized footer public static final int NOVIEW = 3; //this is specialized Ad view public static final int ADVIEW = 4; public static final int CUSTOMVIEW = 5; } protected enum AdapterAnimationType { AlphaIn, SlideInBottom, ScaleIn, SlideInLeft, SlideInRight, } /** * Animations when loading the adapter * * @param view the view * @param type the type of the animation * @return the animator in array */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) protected Animator[] getAdapterAnimations(View view, AdapterAnimationType type) { if (type == AdapterAnimationType.ScaleIn) { ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", .5f, 1f); ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", .5f, 1f); return new ObjectAnimator[]{scaleX, scaleY}; } else if (type == AdapterAnimationType.AlphaIn) { return new Animator[]{ObjectAnimator.ofFloat(view, "alpha", .5f, 1f)}; } else if (type == AdapterAnimationType.SlideInBottom) { return new Animator[]{ ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0) }; } else if (type == AdapterAnimationType.SlideInLeft) { return new Animator[]{ ObjectAnimator.ofFloat(view, "translationX", -view.getRootView().getWidth(), 0) }; } else if (type == AdapterAnimationType.SlideInRight) { return new Animator[]{ ObjectAnimator.ofFloat(view, "translationX", view.getRootView().getWidth(), 0) }; } return null; } /* @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ViewHelper.clear(holder.itemView); } */ @Override public void onItemMove(int fromPosition, int toPosition) { notifyItemMoved(fromPosition, toPosition); } @Override public void onItemDismiss(int position) { if (hasHeaderView() && getItemViewType(position) == VIEW_TYPES.HEADER) return; if (enableLoadMore() && getItemViewType(position) == VIEW_TYPES.FOOTER) return; notifyDataSetChanged(); } protected OnStartDragListener mDragStartListener = null; /** * Listener for manual initiation of a drag. */ public interface OnStartDragListener { /** * Called when a view is requesting a start of a drag. * * @param viewHolder The holder of the view to drag. */ void onStartDrag(RecyclerView.ViewHolder viewHolder); } }