/** * Copyright 2013 Joan Zapata * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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 com.sxjs.common.base.baseadapter; import android.animation.Animator; import android.content.Context; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.LayoutParams; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import com.sxjs.common.base.baseadapter.animation.AlphaInAnimation; import com.sxjs.common.base.baseadapter.animation.BaseAnimation; import com.sxjs.common.base.baseadapter.animation.ScaleInAnimation; import com.sxjs.common.base.baseadapter.animation.SlideInBottomAnimation; import com.sxjs.common.base.baseadapter.animation.SlideInLeftAnimation; import com.sxjs.common.base.baseadapter.animation.SlideInRightAnimation; import com.sxjs.common.base.baseadapter.entity.IExpandable; import com.sxjs.common.base.baseadapter.loadmore.LoadMoreView; import com.sxjs.common.base.baseadapter.loadmore.SimpleLoadMoreView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * https://github.com/CymChad/BaseRecyclerViewAdapterHelper */ public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> { //load more private boolean mNextLoadEnable = false; private boolean mLoadMoreEnable = false; private boolean mLoading = false; private LoadMoreView mLoadMoreView = new SimpleLoadMoreView(); private RequestLoadMoreListener mRequestLoadMoreListener; //Animation /** * Use with {@link #openLoadAnimation} */ public static final int ALPHAIN = 0x00000001; /** * Use with {@link #openLoadAnimation} */ public static final int SCALEIN = 0x00000002; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_BOTTOM = 0x00000003; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_LEFT = 0x00000004; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_RIGHT = 0x00000005; private OnItemClickListener mOnItemClickListener; private OnItemLongClickListener mOnItemLongClickListener; private OnItemChildClickListener mOnItemChildClickListener; private OnItemChildLongClickListener mOnItemChildLongClickListener; @IntDef({ALPHAIN, SCALEIN, SLIDEIN_BOTTOM, SLIDEIN_LEFT, SLIDEIN_RIGHT}) @Retention(RetentionPolicy.SOURCE) public @interface AnimationType { } private boolean mFirstOnlyEnable = true; private boolean mOpenAnimationEnable = false; private Interpolator mInterpolator = new LinearInterpolator(); private int mDuration = 300; private int mLastPosition = -1; private BaseAnimation mCustomAnimation; private BaseAnimation mSelectAnimation = new AlphaInAnimation(); //header footer private LinearLayout mHeaderLayout; private LinearLayout mFooterLayout; //empty private FrameLayout mEmptyLayout; private boolean mIsUseEmpty = true; private boolean mHeadAndEmptyEnable; private boolean mFootAndEmptyEnable; protected static final String TAG = BaseQuickAdapter.class.getSimpleName(); protected Context mContext; protected int mLayoutResId; protected LayoutInflater mLayoutInflater; protected List<T> mData; public static final int HEADER_VIEW = 0x00000111; public static final int LOADING_VIEW = 0x00000222; public static final int FOOTER_VIEW = 0x00000333; public static final int EMPTY_VIEW = 0x00000555; private RecyclerView mRecyclerView; protected RecyclerView getRecyclerView() { return mRecyclerView; } private void setRecyclerView(RecyclerView recyclerView) { mRecyclerView = recyclerView; } private void checkNotNull() { if (getRecyclerView() == null) { throw new RuntimeException("please bind recyclerView first!"); } } /** * same as recyclerView.setAdapter(), and save the instance of recyclerView */ public void bindToRecyclerView(RecyclerView recyclerView) { if (getRecyclerView() != null) { throw new RuntimeException("Don't bind twice"); } setRecyclerView(recyclerView); getRecyclerView().setAdapter(this); } /** * @see #setOnLoadMoreListener(RequestLoadMoreListener, RecyclerView) * @deprecated This method is because it can lead to crash: always call this method while RecyclerView is computing a layout or scrolling. * Please use {@link #setOnLoadMoreListener(RequestLoadMoreListener, RecyclerView)} */ @Deprecated public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener) { openLoadMore(requestLoadMoreListener); } private void openLoadMore(RequestLoadMoreListener requestLoadMoreListener) { this.mRequestLoadMoreListener = requestLoadMoreListener; mNextLoadEnable = true; mLoadMoreEnable = true; mLoading = false; } public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener, RecyclerView recyclerView) { openLoadMore(requestLoadMoreListener); if (getRecyclerView() == null) { setRecyclerView(recyclerView); } } /** * bind recyclerView {@link #bindToRecyclerView(RecyclerView)} before use! * * @see #disableLoadMoreIfNotFullPage(RecyclerView) */ public void disableLoadMoreIfNotFullPage() { checkNotNull(); disableLoadMoreIfNotFullPage(getRecyclerView()); } /** * check if full page after {@link #setNewData(List)}, if full, it will enable load more again. * * @param recyclerView your recyclerView * @see #setNewData(List) */ public void disableLoadMoreIfNotFullPage(RecyclerView recyclerView) { if (recyclerView == null) return; RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null) return; if (manager instanceof LinearLayoutManager) { final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager; recyclerView.postDelayed(new Runnable() { @Override public void run() { if ((linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1) != getItemCount()) { setEnableLoadMore(true); } } }, 50); } else if (manager instanceof StaggeredGridLayoutManager) { final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) manager; recyclerView.postDelayed(new Runnable() { @Override public void run() { final int[] positions = new int[staggeredGridLayoutManager.getSpanCount()]; staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(positions); int pos = getTheBiggestNumber(positions) + 1; if (pos != getItemCount()) { setEnableLoadMore(true); } } }, 50); } } private int getTheBiggestNumber(int[] numbers) { int tmp = -1; if (numbers == null || numbers.length == 0) { return tmp; } for (int num : numbers) { if (num > tmp) { tmp = num; } } return tmp; } public void setNotDoAnimationCount(int count) { mLastPosition = count; } /** * Set custom load more * * @param loadingView */ public void setLoadMoreView(LoadMoreView loadingView) { this.mLoadMoreView = loadingView; } /** * Load more view count * * @return 0 or 1 */ public int getLoadMoreViewCount() { if (mRequestLoadMoreListener == null || !mLoadMoreEnable) { return 0; } if (!mNextLoadEnable && mLoadMoreView.isLoadEndMoreGone()) { return 0; } if (mData.size() == 0) { return 0; } return 1; } /** * @return Whether the Adapter is actively showing load * progress. */ public boolean isLoading() { return mLoading; } /** * Refresh end, no more data */ public void loadMoreEnd() { loadMoreEnd(false); } /** * Refresh end, no more data * * @param gone if true gone the load more view */ public void loadMoreEnd(boolean gone) { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; mNextLoadEnable = false; mLoadMoreView.setLoadMoreEndGone(gone); if (gone) { notifyItemRemoved(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } else { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END); notifyItemChanged(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } } /** * Refresh complete */ public void loadMoreComplete() { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } /** * Refresh failed */ public void loadMoreFail() { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_FAIL); notifyItemChanged(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } /** * Set the enabled state of load more. * * @param enable True if load more is enabled, false otherwise. */ public void setEnableLoadMore(boolean enable) { int oldLoadMoreCount = getLoadMoreViewCount(); mLoadMoreEnable = enable; int newLoadMoreCount = getLoadMoreViewCount(); if (oldLoadMoreCount == 1) { if (newLoadMoreCount == 0) { notifyItemRemoved(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } } else { if (newLoadMoreCount == 1) { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemInserted(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } } } /** * Returns the enabled status for load more. * * @return True if load more is enabled, false otherwise. */ public boolean isLoadMoreEnable() { return mLoadMoreEnable; } /** * Sets the duration of the animation. * * @param duration The length of the animation, in milliseconds. */ public void setDuration(int duration) { mDuration = duration; } /** * Same as QuickAdapter#QuickAdapter(Context,int) but with * some initialization data. * * @param layoutResId The layout resource id of each item. * @param data A new list is created out of this one to avoid mutable list */ public BaseQuickAdapter(int layoutResId, List<T> data) { this.mData = data == null ? new ArrayList<T>() : data; if (layoutResId != 0) { this.mLayoutResId = layoutResId; } } public BaseQuickAdapter(List<T> data) { this(0, data); } public BaseQuickAdapter(int layoutResId) { this(layoutResId, null); } public BaseQuickAdapter(){ this(0, null); } /** * setting up a new instance to data; * * @param data */ public void setNewData(List<T> data) { this.mData = data == null ? new ArrayList<T>() : data; if (mRequestLoadMoreListener != null) { mNextLoadEnable = true; mLoadMoreEnable = true; mLoading = false; mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); } mLastPosition = -1; notifyDataSetChanged(); } /** * insert a item associated with the specified position of adapter * * @param position * @param item * @deprecated use {@link #addData(int, Object)} instead */ @Deprecated public void add(int position, T item) { addData(position, item); } /** * add one new data in to certain location * * @param position */ public void addData(int position, T data) { mData.add(position, data); notifyItemInserted(position + getHeaderLayoutCount()); compatibilityDataSizeChanged(1); } /** * add one new data */ public void addData(T data) { mData.add(data); notifyItemInserted(mData.size() + getHeaderLayoutCount()); compatibilityDataSizeChanged(1); } /** * remove the item associated with the specified position of adapter * * @param position */ public void remove(int position) { mData.remove(position); notifyItemRemoved(position + getHeaderLayoutCount()); compatibilityDataSizeChanged(0); notifyItemRangeChanged(position, mData.size() - position); } /** * change data */ public void setData(int index, T data) { mData.set(index, data); notifyItemChanged(index + getHeaderLayoutCount()); } /** * add new data in to certain location * * @param position */ public void addData(int position, List<T> data) { mData.addAll(position, data); notifyItemRangeInserted(position + getHeaderLayoutCount(), data.size()); compatibilityDataSizeChanged(data.size()); } /** * additional data; * * @param newData */ public void addData(List<T> newData) { this.mData.addAll(newData); notifyItemRangeInserted(mData.size() - newData.size() + getHeaderLayoutCount(), newData.size()); compatibilityDataSizeChanged(newData.size()); } /** * compatible getLoadMoreViewCount and getEmptyViewCount may change * * @param size Need compatible data size */ private void compatibilityDataSizeChanged(int size) { final int dataSize = mData == null ? 0 : mData.size(); if (dataSize == size) { notifyDataSetChanged(); } } /** * Get the data of list * * @return */ public List<T> getData() { return mData; } /** * Get the data item associated with the specified position in the data set. * * @param position Position of the item whose data we want within the adapter's * data set. * @return The data at the specified position. */ public T getItem(int position) { if (position != -1) return mData.get(position); else return null; } /** * if setHeadView will be return 1 if not will be return 0. * notice: Deprecated! Use {@link ViewGroup#getChildCount()} of {@link #getHeaderLayout()} to replace. * * @return */ @Deprecated public int getHeaderViewsCount() { return getHeaderLayoutCount(); } /** * if mFooterLayout will be return 1 or not will be return 0. * notice: Deprecated! Use {@link ViewGroup#getChildCount()} of {@link #getFooterLayout()} to replace. * * @return */ @Deprecated public int getFooterViewsCount() { return getFooterLayoutCount(); } /** * if addHeaderView will be return 1, if not will be return 0 */ public int getHeaderLayoutCount() { if (mHeaderLayout == null || mHeaderLayout.getChildCount() == 0) { return 0; } return 1; } /** * if addFooterView will be return 1, if not will be return 0 */ public int getFooterLayoutCount() { if (mFooterLayout == null || mFooterLayout.getChildCount() == 0) { return 0; } return 1; } /** * if show empty view will be return 1 or not will be return 0 * * @return */ public int getEmptyViewCount() { if (mEmptyLayout == null || mEmptyLayout.getChildCount() == 0) { return 0; } if (!mIsUseEmpty) { return 0; } if (mData.size() != 0) { return 0; } return 1; } @Override public int getItemCount() { int count; if (getEmptyViewCount() == 1) { count = 1; if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) { count++; } if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) { count++; } } else { count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount(); } return count; } @Override public int getItemViewType(int position) { if (getEmptyViewCount() == 1) { boolean header = mHeadAndEmptyEnable && getHeaderLayoutCount() != 0; switch (position) { case 0: if (header) { return HEADER_VIEW; } else { return EMPTY_VIEW; } case 1: if (header) { return EMPTY_VIEW; } else { return FOOTER_VIEW; } case 2: return FOOTER_VIEW; default: return EMPTY_VIEW; } } autoLoadMore(position); int numHeaders = getHeaderLayoutCount(); if (position < numHeaders) { return HEADER_VIEW; } else { int adjPosition = position - numHeaders; int adapterCount = mData.size(); if (adjPosition < adapterCount) { return getDefItemViewType(adjPosition); } else { adjPosition = adjPosition - adapterCount; int numFooters = getFooterLayoutCount(); if (adjPosition < numFooters) { return FOOTER_VIEW; } else { return LOADING_VIEW; } } } } protected int getDefItemViewType(int position) { return super.getItemViewType(position); } @Override public K onCreateViewHolder(ViewGroup parent, int viewType) { K baseViewHolder = null; this.mContext = parent.getContext(); this.mLayoutInflater = LayoutInflater.from(mContext); switch (viewType) { case LOADING_VIEW: baseViewHolder = getLoadingView(parent); break; case HEADER_VIEW: baseViewHolder = createBaseViewHolder(mHeaderLayout); break; case EMPTY_VIEW: baseViewHolder = createBaseViewHolder(mEmptyLayout); break; case FOOTER_VIEW: baseViewHolder = createBaseViewHolder(mFooterLayout); break; default: baseViewHolder = onCreateDefViewHolder(parent, viewType); bindViewClickListener(baseViewHolder); } baseViewHolder.setAdapter(this); return baseViewHolder; } private K getLoadingView(ViewGroup parent) { View view = getItemView(mLoadMoreView.getLayoutId(), parent); K holder = createBaseViewHolder(view); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mLoadMoreView.getLoadMoreStatus() == LoadMoreView.STATUS_FAIL) { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(getHeaderLayoutCount() + mData.size() + getFooterLayoutCount()); } } }); return holder; } /** * Called when a view created by this adapter has been attached to a window. * simple to solve item will layout using all * {@link #setFullSpan(RecyclerView.ViewHolder)} * * @param holder */ @Override public void onViewAttachedToWindow(K holder) { super.onViewAttachedToWindow(holder); int type = holder.getItemViewType(); if (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW) { setFullSpan(holder); } else { addAnimation(holder); } } /** * When set to true, the item will layout using all span area. That means, if orientation * is vertical, the view will have full width; if orientation is horizontal, the view will * have full height. * if the hold view use StaggeredGridLayoutManager they should using all span area * * @param holder True if this item should traverse all spans. */ protected void setFullSpan(RecyclerView.ViewHolder holder) { if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder .itemView.getLayoutParams(); params.setFullSpan(true); } } @Override public void onAttachedToRecyclerView(final RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int type = getItemViewType(position); if (mSpanSizeLookup == null) return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW) ? gridManager.getSpanCount() : 1 ; else return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW) ? gridManager.getSpanCount() : mSpanSizeLookup.getSpanSize(gridManager, position - getHeaderLayoutCount()); } }); } } private SpanSizeLookup mSpanSizeLookup; public interface SpanSizeLookup { int getSpanSize(GridLayoutManager gridLayoutManager, int position ); } /** * @param spanSizeLookup instance to be used to query number of spans occupied by each item */ public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) { this.mSpanSizeLookup = spanSizeLookup; } /** * To bind different types of holder and solve different the bind events * * @param holder * @param positions * @see #getDefItemViewType(int) */ @Override public void onBindViewHolder(K holder, int positions) { int viewType = holder.getItemViewType(); switch (viewType) { case 0: convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()),holder.getLayoutPosition() - getHeaderLayoutCount()); break; case LOADING_VIEW: mLoadMoreView.convert(holder); break; case HEADER_VIEW: break; case EMPTY_VIEW: break; case FOOTER_VIEW: break; default: convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()),holder.getLayoutPosition() - getHeaderLayoutCount()); break; } } private void bindViewClickListener(final BaseViewHolder baseViewHolder) { if (baseViewHolder == null) { return; } final View view = baseViewHolder.getConvertView(); if (view == null) { return; } view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getOnItemClickListener() != null&&baseViewHolder!=null) { getOnItemClickListener().onItemClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount()); } } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (getOnItemLongClickListener() != null&&baseViewHolder!=null) { return getOnItemLongClickListener().onItemLongClick(BaseQuickAdapter.this, v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount()); } return false; } }); } protected K onCreateDefViewHolder(ViewGroup parent, int viewType) { return createBaseViewHolder(parent, mLayoutResId); } protected K createBaseViewHolder(ViewGroup parent, int layoutResId) { return createBaseViewHolder(getItemView(layoutResId, parent)); } /** * if you want to use subclass of BaseViewHolder in the adapter, * you must override the method to create new ViewHolder. * * @param view view * @return new ViewHolder */ protected K createBaseViewHolder(View view) { Class temp = getClass(); Class z = null; while (z == null && null != temp) { z = getInstancedGenericKClass(temp); temp = temp.getSuperclass(); } K k = createGenericKInstance(z, view); return null != k ? k : (K) new BaseViewHolder(view); } /** * try to create Generic K instance * * @param z * @param view * @return */ private K createGenericKInstance(Class z, View view) { try { Constructor constructor; String buffer = Modifier.toString(z.getModifiers()); String className = z.getName(); // inner and unstatic class if (className.contains("$") && !buffer.contains("static")) { constructor = z.getDeclaredConstructor(getClass(), View.class); return (K) constructor.newInstance(this, view); } else { constructor = z.getDeclaredConstructor(View.class); return (K) constructor.newInstance(view); } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } /** * get generic parameter K * * @param z * @return */ private Class getInstancedGenericKClass(Class z) { Type type = z.getGenericSuperclass(); if (type instanceof ParameterizedType) { Type[] types = ((ParameterizedType) type).getActualTypeArguments(); for (Type temp : types) { if (temp instanceof Class) { Class tempClass = (Class) temp; if (BaseViewHolder.class.isAssignableFrom(tempClass)) { return tempClass; } } } } return null; } /** * Return root layout of header */ public LinearLayout getHeaderLayout() { return mHeaderLayout; } /** * Return root layout of footer */ public LinearLayout getFooterLayout() { return mFooterLayout; } /** * Append header to the rear of the mHeaderLayout. * * @param header */ public int addHeaderView(View header) { return addHeaderView(header, -1); } /** * Add header view to mHeaderLayout and set header view position in mHeaderLayout. * When index = -1 or index >= child count in mHeaderLayout, * the effect of this method is the same as that of {@link #addHeaderView(View)}. * * @param header * @param index the position in mHeaderLayout of this header. * When index = -1 or index >= child count in mHeaderLayout, * the effect of this method is the same as that of {@link #addHeaderView(View)}. */ public int addHeaderView(View header, int index) { return addHeaderView(header, index, LinearLayout.VERTICAL); } /** * @param header * @param index * @param orientation */ public int addHeaderView(View header, int index, int orientation) { if (mHeaderLayout == null) { mHeaderLayout = new LinearLayout(header.getContext()); if (orientation == LinearLayout.VERTICAL) { mHeaderLayout.setOrientation(LinearLayout.VERTICAL); mHeaderLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } else { mHeaderLayout.setOrientation(LinearLayout.HORIZONTAL); mHeaderLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT)); } } final int childCount = mHeaderLayout.getChildCount(); if (index < 0 || index > childCount) { index = childCount; } mHeaderLayout.addView(header, index); if (mHeaderLayout.getChildCount() == 1) { int position = getHeaderViewPosition(); if (position != -1) { notifyItemInserted(position); } } return index; } public int setHeaderView(View header) { return setHeaderView(header, 0, LinearLayout.VERTICAL); } public int setHeaderView(View header, int index) { return setHeaderView(header, index, LinearLayout.VERTICAL); } public int setHeaderView(View header, int index, int orientation) { if (mHeaderLayout == null || mHeaderLayout.getChildCount() <= index) { return addHeaderView(header, index, orientation); } else { mHeaderLayout.removeViewAt(index); mHeaderLayout.addView(header, index); return index; } } /** * Append footer to the rear of the mFooterLayout. * * @param footer */ public int addFooterView(View footer) { return addFooterView(footer, -1, LinearLayout.VERTICAL); } public int addFooterView(View footer, int index) { return addFooterView(footer, index, LinearLayout.VERTICAL); } /** * Add footer view to mFooterLayout and set footer view position in mFooterLayout. * When index = -1 or index >= child count in mFooterLayout, * the effect of this method is the same as that of {@link #addFooterView(View)}. * * @param footer * @param index the position in mFooterLayout of this footer. * When index = -1 or index >= child count in mFooterLayout, * the effect of this method is the same as that of {@link #addFooterView(View)}. */ public int addFooterView(View footer, int index, int orientation) { if (mFooterLayout == null) { mFooterLayout = new LinearLayout(footer.getContext()); if (orientation == LinearLayout.VERTICAL) { mFooterLayout.setOrientation(LinearLayout.VERTICAL); mFooterLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } else { mFooterLayout.setOrientation(LinearLayout.HORIZONTAL); mFooterLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT)); } } final int childCount = mFooterLayout.getChildCount(); if (index < 0 || index > childCount) { index = childCount; } mFooterLayout.addView(footer, index); if (mFooterLayout.getChildCount() == 1) { int position = getFooterViewPosition(); if (position != -1) { notifyItemInserted(position); } } return index; } public int setFooterView(View header) { return setFooterView(header, 0, LinearLayout.VERTICAL); } public int setFooterView(View header, int index) { return setFooterView(header, index, LinearLayout.VERTICAL); } public int setFooterView(View header, int index, int orientation) { if (mFooterLayout == null || mFooterLayout.getChildCount() <= index) { return addFooterView(header, index, orientation); } else { mFooterLayout.removeViewAt(index); mFooterLayout.addView(header, index); return index; } } /** * remove header view from mHeaderLayout. * When the child count of mHeaderLayout is 0, mHeaderLayout will be set to null. * * @param header */ public void removeHeaderView(View header) { if (getHeaderLayoutCount() == 0) return; mHeaderLayout.removeView(header); if (mHeaderLayout.getChildCount() == 0) { int position = getHeaderViewPosition(); if (position != -1) { notifyItemRemoved(position); } } } /** * remove footer view from mFooterLayout, * When the child count of mFooterLayout is 0, mFooterLayout will be set to null. * * @param footer */ public void removeFooterView(View footer) { if (getFooterLayoutCount() == 0) return; mFooterLayout.removeView(footer); if (mFooterLayout.getChildCount() == 0) { int position = getFooterViewPosition(); if (position != -1) { notifyItemRemoved(position); } } } /** * remove all header view from mHeaderLayout and set null to mHeaderLayout */ public void removeAllHeaderView() { if (getHeaderLayoutCount() == 0) return; mHeaderLayout.removeAllViews(); int position = getHeaderViewPosition(); if (position != -1) { notifyItemRemoved(position); } } /** * remove all footer view from mFooterLayout and set null to mFooterLayout */ public void removeAllFooterView() { if (getFooterLayoutCount() == 0) return; mFooterLayout.removeAllViews(); int position = getFooterViewPosition(); if (position != -1) { notifyItemRemoved(position); } } private int getHeaderViewPosition() { //Return to header view notify position if (getEmptyViewCount() == 1) { if (mHeadAndEmptyEnable) { return 0; } } else { return 0; } return -1; } private int getFooterViewPosition() { //Return to footer view notify position if (getEmptyViewCount() == 1) { int position = 1; if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) { position++; } if (mFootAndEmptyEnable) { return position; } } else { return getHeaderLayoutCount() + mData.size(); } return -1; } public void setEmptyView(int layoutResId, ViewGroup viewGroup) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutResId, viewGroup, false); setEmptyView(view); } /** * bind recyclerView {@link #bindToRecyclerView(RecyclerView)} before use! * * @see #bindToRecyclerView(RecyclerView) */ public void setEmptyView(int layoutResId) { checkNotNull(); setEmptyView(layoutResId, getRecyclerView()); } public void setEmptyView(View emptyView) { boolean insert = false; if (mEmptyLayout == null) { mEmptyLayout = new FrameLayout(emptyView.getContext()); final LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); final ViewGroup.LayoutParams lp = emptyView.getLayoutParams(); if (lp != null) { layoutParams.width = lp.width; layoutParams.height = lp.height; } mEmptyLayout.setLayoutParams(layoutParams); insert = true; } mEmptyLayout.removeAllViews(); mEmptyLayout.addView(emptyView); mIsUseEmpty = true; if (insert) { if (getEmptyViewCount() == 1) { int position = 0; if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) { position++; } notifyItemInserted(position); } } } /** * Call before {@link RecyclerView#setAdapter(RecyclerView.Adapter)} * * @param isHeadAndEmpty false will not show headView if the data is empty true will show emptyView and headView */ public void setHeaderAndEmpty(boolean isHeadAndEmpty) { setHeaderFooterEmpty(isHeadAndEmpty, false); } /** * set emptyView show if adapter is empty and want to show headview and footview * Call before {@link RecyclerView#setAdapter(RecyclerView.Adapter)} * * @param isHeadAndEmpty * @param isFootAndEmpty */ public void setHeaderFooterEmpty(boolean isHeadAndEmpty, boolean isFootAndEmpty) { mHeadAndEmptyEnable = isHeadAndEmpty; mFootAndEmptyEnable = isFootAndEmpty; } /** * Set whether to use empty view * * @param isUseEmpty */ public void isUseEmpty(boolean isUseEmpty) { mIsUseEmpty = isUseEmpty; } /** * When the current adapter is empty, the BaseQuickAdapter can display a special view * called the empty view. The empty view is used to provide feedback to the user * that no data is available in this AdapterView. * * @return The view to show if the adapter is empty. */ public View getEmptyView() { return mEmptyLayout; } private int mAutoLoadMoreSize = 1; public void setAutoLoadMoreSize(int autoLoadMoreSize) { if (autoLoadMoreSize > 1) { mAutoLoadMoreSize = autoLoadMoreSize; } } private void autoLoadMore(int position) { if (getLoadMoreViewCount() == 0) { return; } if (position < getItemCount() - mAutoLoadMoreSize) { return; } if (mLoadMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) { return; } mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING); if (!mLoading) { mLoading = true; if (getRecyclerView() != null) { getRecyclerView().post(new Runnable() { @Override public void run() { mRequestLoadMoreListener.onLoadMoreRequested(); } }); } else { mRequestLoadMoreListener.onLoadMoreRequested(); } } } /** * add animation when you want to show time * * @param holder */ private void addAnimation(RecyclerView.ViewHolder holder) { if (mOpenAnimationEnable) { if (!mFirstOnlyEnable || holder.getLayoutPosition() > mLastPosition) { BaseAnimation animation = null; if (mCustomAnimation != null) { animation = mCustomAnimation; } else { animation = mSelectAnimation; } for (Animator anim : animation.getAnimators(holder.itemView)) { startAnim(anim, holder.getLayoutPosition()); } mLastPosition = holder.getLayoutPosition(); } } } /** * set anim to start when loading * * @param anim * @param index */ protected void startAnim(Animator anim, int index) { anim.setDuration(mDuration).start(); anim.setInterpolator(mInterpolator); } /** * @param layoutResId ID for an XML layout resource to load * @param parent Optional view to be the parent of the generated hierarchy or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy * @return view will be return */ protected View getItemView(int layoutResId, ViewGroup parent) { return mLayoutInflater.inflate(layoutResId, parent, false); } public interface RequestLoadMoreListener { void onLoadMoreRequested(); } /** * Set the view animation type. * * @param animationType One of {@link #ALPHAIN}, {@link #SCALEIN}, {@link #SLIDEIN_BOTTOM}, * {@link #SLIDEIN_LEFT}, {@link #SLIDEIN_RIGHT}. */ public void openLoadAnimation(@AnimationType int animationType) { this.mOpenAnimationEnable = true; mCustomAnimation = null; switch (animationType) { case ALPHAIN: mSelectAnimation = new AlphaInAnimation(); break; case SCALEIN: mSelectAnimation = new ScaleInAnimation(); break; case SLIDEIN_BOTTOM: mSelectAnimation = new SlideInBottomAnimation(); break; case SLIDEIN_LEFT: mSelectAnimation = new SlideInLeftAnimation(); break; case SLIDEIN_RIGHT: mSelectAnimation = new SlideInRightAnimation(); break; default: break; } } /** * Set Custom ObjectAnimator * * @param animation ObjectAnimator */ public void openLoadAnimation(BaseAnimation animation) { this.mOpenAnimationEnable = true; this.mCustomAnimation = animation; } /** * To open the animation when loading */ public void openLoadAnimation() { this.mOpenAnimationEnable = true; } /** * {@link #addAnimation(RecyclerView.ViewHolder)} * * @param firstOnly true just show anim when first loading false show anim when load the data every time */ public void isFirstOnly(boolean firstOnly) { this.mFirstOnlyEnable = firstOnly; } /** * Implement this method and use the helper to adapt the view to the given item. * * @param helper A fully initialized helper. * @param item The item that needs to be displayed. */ protected abstract void convert(K helper, T item,int position); /** * get the specific view by position,e.g. getViewByPosition(2, R.id.textView) * <p> * bind recyclerView {@link #bindToRecyclerView(RecyclerView)} before use! * * @see #bindToRecyclerView(RecyclerView) */ public View getViewByPosition(int position, int viewId) { checkNotNull(); return getViewByPosition(getRecyclerView(), position, viewId); } public View getViewByPosition(RecyclerView recyclerView, int position, int viewId) { if (recyclerView == null) { return null; } BaseViewHolder viewHolder = (BaseViewHolder) recyclerView.findViewHolderForLayoutPosition(position); return viewHolder.getView(viewId); } /** * Get the row id associated with the specified position in the list. * * @param position The position of the item within the adapter's data set whose row id we want. * @return The id of the item at the specified position. */ @Override public long getItemId(int position) { return position; } private int recursiveExpand(int position, @NonNull List list) { int count = 0; int pos = position + list.size() - 1; for (int i = list.size() - 1; i >= 0; i--, pos--) { if (list.get(i) instanceof IExpandable) { IExpandable item = (IExpandable) list.get(i); if (item.isExpanded() && hasSubItems(item)) { List subList = item.getSubItems(); mData.addAll(pos + 1, subList); int subItemCount = recursiveExpand(pos + 1, subList); count += subItemCount; } } } return count; } /** * Expand an expandable item * * @param position position of the item * @param animate expand items with animation * @param shouldNotify notify the RecyclerView to rebind items, <strong>false</strong> if you want to do it * yourself. * @return the number of items that have been added. */ public int expand(@IntRange(from = 0) int position, boolean animate, boolean shouldNotify) { position -= getHeaderLayoutCount(); IExpandable expandable = getExpandableItem(position); if (expandable == null) { return 0; } if (!hasSubItems(expandable)) { expandable.setExpanded(false); return 0; } int subItemCount = 0; if (!expandable.isExpanded()) { List list = expandable.getSubItems(); mData.addAll(position + 1, list); subItemCount += recursiveExpand(position + 1, list); expandable.setExpanded(true); subItemCount += list.size(); } int parentPos = position + getHeaderLayoutCount(); if (shouldNotify) { if (animate) { notifyItemChanged(parentPos); notifyItemRangeInserted(parentPos + 1, subItemCount); } else { notifyDataSetChanged(); } } return subItemCount; } /** * Expand an expandable item * * @param position position of the item, which includes the header layout count. * @param animate expand items with animation * @return the number of items that have been added. */ public int expand(@IntRange(from = 0) int position, boolean animate) { return expand(position, animate, true); } /** * Expand an expandable item with animation. * * @param position position of the item, which includes the header layout count. * @return the number of items that have been added. */ public int expand(@IntRange(from = 0) int position) { return expand(position, true, true); } public int expandAll(int position, boolean animate, boolean notify) { position -= getHeaderLayoutCount(); T endItem = null; if (position + 1 < this.mData.size()) { endItem = getItem(position + 1); } IExpandable expandable = getExpandableItem(position); if (!hasSubItems(expandable)) { return 0; } int count = expand(position + getHeaderLayoutCount(), false, false); for (int i = position + 1; i < this.mData.size(); i++) { T item = getItem(i); if (item == endItem) { break; } if (isExpandable(item)) { count += expand(i + getHeaderLayoutCount(), false, false); } } if (notify) { if (animate) { notifyItemRangeInserted(position + getHeaderLayoutCount() + 1, count); } else { notifyDataSetChanged(); } } return count; } /** * expand the item and all its subItems * * @param position position of the item, which includes the header layout count. * @param init whether you are initializing the recyclerView or not. * if <strong>true</strong>, it won't notify recyclerView to redraw UI. * @return the number of items that have been added to the adapter. */ public int expandAll(int position, boolean init) { return expandAll(position, true, !init); } private int recursiveCollapse(@IntRange(from = 0) int position) { T item = getItem(position); if (!isExpandable(item)) { return 0; } IExpandable expandable = (IExpandable) item; int subItemCount = 0; if (expandable.isExpanded()) { List<T> subItems = expandable.getSubItems(); for (int i = subItems.size() - 1; i >= 0; i--) { T subItem = subItems.get(i); int pos = getItemPosition(subItem); if (pos < 0) { continue; } if (subItem instanceof IExpandable) { subItemCount += recursiveCollapse(pos); } mData.remove(pos); subItemCount++; } } return subItemCount; } /** * Collapse an expandable item that has been expanded.. * * @param position the position of the item, which includes the header layout count. * @param animate collapse with animation or not. * @param notify notify the recyclerView refresh UI or not. * @return the number of subItems collapsed. */ public int collapse(@IntRange(from = 0) int position, boolean animate, boolean notify) { position -= getHeaderLayoutCount(); IExpandable expandable = getExpandableItem(position); if (expandable == null) { return 0; } int subItemCount = recursiveCollapse(position); expandable.setExpanded(false); int parentPos = position + getHeaderLayoutCount(); if (notify) { if (animate) { notifyItemChanged(parentPos); notifyItemRangeRemoved(parentPos + 1, subItemCount); } else { notifyDataSetChanged(); } } return subItemCount; } /** * Collapse an expandable item that has been expanded.. * * @param position the position of the item, which includes the header layout count. * @return the number of subItems collapsed. */ public int collapse(@IntRange(from = 0) int position) { return collapse(position, true, true); } /** * Collapse an expandable item that has been expanded.. * * @param position the position of the item, which includes the header layout count. * @return the number of subItems collapsed. */ public int collapse(@IntRange(from = 0) int position, boolean animate) { return collapse(position, animate, true); } private int getItemPosition(T item) { return item != null && mData != null && !mData.isEmpty() ? mData.indexOf(item) : -1; } private boolean hasSubItems(IExpandable item) { List list = item.getSubItems(); return list != null && list.size() > 0; } public boolean isExpandable(T item) { return item != null && item instanceof IExpandable; } private IExpandable getExpandableItem(int position) { T item = getItem(position); if (isExpandable(item)) { return (IExpandable) item; } else { return null; } } /** * Get the parent item position of the IExpandable item * * @return return the closest parent item position of the IExpandable. * if the IExpandable item's level is 0, return itself position. * if the item's level is negative which mean do not implement this, return a negative * if the item is not exist in the data list, return a negative. */ public int getParentPosition(@NonNull T item) { int position = getItemPosition(item); if (position == -1) { return -1; } // if the item is IExpandable, return a closest IExpandable item position whose level smaller than this. // if it is not, return the closest IExpandable item position whose level is not negative int level; if (item instanceof IExpandable) { level = ((IExpandable) item).getLevel(); } else { level = Integer.MAX_VALUE; } if (level == 0) { return position; } else if (level == -1) { return -1; } for (int i = position; i >= 0; i--) { T temp = mData.get(i); if (temp instanceof IExpandable) { IExpandable expandable = (IExpandable) temp; if (expandable.getLevel() >= 0 && expandable.getLevel() < level) { return i; } } } return -1; } /** * Interface definition for a callback to be invoked when an itemchild in this * view has been clicked */ public interface OnItemChildClickListener { /** * callback method to be invoked when an item in this view has been * click and held * * @param view The view whihin the ItemView that was clicked * @param position The position of the view int the adapter * @return true if the callback consumed the long click ,false otherwise */ boolean onItemChildClick(BaseQuickAdapter adapter, View view, int position); } /** * Interface definition for a callback to be invoked when an childView in this * view has been clicked and held. */ public interface OnItemChildLongClickListener { /** * callback method to be invoked when an item in this view has been * click and held * * @param view The childView whihin the itemView that was clicked and held. * @param position The position of the view int the adapter * @return true if the callback consumed the long click ,false otherwise */ void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position); } /** * Interface definition for a callback to be invoked when an item in this * view has been clicked and held. */ public interface OnItemLongClickListener { /** * callback method to be invoked when an item in this view has been * click and held * * @param adapter the adpater * @param view The view whihin the RecyclerView that was clicked and held. * @param position The position of the view int the adapter * @return true if the callback consumed the long click ,false otherwise */ boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position); } /** * Interface definition for a callback to be invoked when an item in this * RecyclerView itemView has been clicked. */ public interface OnItemClickListener { /** * Callback method to be invoked when an item in this RecyclerView has * been clicked. * * @param adapter the adpater * @param view The itemView within the RecyclerView that was clicked (this * will be a view provided by the adapter) * @param position The position of the view in the adapter. */ void onItemClick(BaseQuickAdapter adapter, View view, int position); } /** * Register a callback to be invoked when an item in this RecyclerView has * been clicked. * * @param listener The callback that will be invoked. */ public void setOnItemClickListener(@Nullable OnItemClickListener listener) { mOnItemClickListener = listener; } /** * Register a callback to be invoked when an itemchild in View has * been clicked * * @param listener The callback that will run */ public void setOnItemChildClickListener(OnItemChildClickListener listener) { mOnItemChildClickListener = listener; } /** * Register a callback to be invoked when an item in this RecyclerView has * been long clicked and held * * @param listener The callback that will run */ public void setOnItemLongClickListener(OnItemLongClickListener listener) { mOnItemLongClickListener = listener; } /** * Register a callback to be invoked when an itemchild in this View has * been long clicked and held * * @param listener The callback that will run */ public void setOnItemChildLongClickListener(OnItemChildLongClickListener listener) { mOnItemChildLongClickListener = listener; } /** * @return The callback to be invoked with an item in this RecyclerView has * been long clicked and held, or null id no callback as been set. */ public final OnItemLongClickListener getOnItemLongClickListener() { return mOnItemLongClickListener; } /** * @return The callback to be invoked with an item in this RecyclerView has * been clicked and held, or null id no callback as been set. */ public final OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; } /** * @return The callback to be invoked with an itemchild in this RecyclerView has * been clicked, or null id no callback has been set. */ @Nullable public final OnItemChildClickListener getOnItemChildClickListener() { return mOnItemChildClickListener; } /** * @return The callback to be invoked with an itemChild in this RecyclerView has * been long clicked, or null id no callback has been set. */ @Nullable public final OnItemChildLongClickListener getmOnItemChildLongClickListener() { return mOnItemChildLongClickListener; } }