/**
* 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;
}
}