/**
* 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.netease.nim.uikit.common.ui.recyclerview.adapter;
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.v7.widget.GridLayoutManager;
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.netease.nim.uikit.common.ui.recyclerview.animation.AlphaInAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.animation.BaseAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.animation.ScaleInAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.animation.SlideInBottomAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.animation.SlideInLeftAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.animation.SlideInRightAnimation;
import com.netease.nim.uikit.common.ui.recyclerview.entity.IExpandable;
import com.netease.nim.uikit.common.ui.recyclerview.holder.BaseViewHolder;
import com.netease.nim.uikit.common.ui.recyclerview.loadmore.LoadMoreView;
import com.netease.nim.uikit.common.ui.recyclerview.loadmore.SimpleLoadMoreView;
import com.netease.nim.uikit.common.ui.recyclerview.util.RecyclerViewUtil;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> implements IRecyclerView {
private boolean isScrolling = false;
//load more
private boolean mNextLoadEnable = false;
private boolean mLoadMoreEnable = false;
private RequestLoadMoreListener mRequestLoadMoreListener;
private boolean mLoading = false;
private LoadMoreView mLoadMoreView = new SimpleLoadMoreView();
private boolean mFirstOnlyEnable = true;
private boolean mOpenAnimationEnable = false;
private Interpolator mInterpolator = new LinearInterpolator();
private int mDuration = 300;
private int mLastPosition = -1;
//@AnimationType
private BaseAnimation mCustomAnimation;
private BaseAnimation mSelectAnimation = new AlphaInAnimation();
//header footer
private LinearLayout mHeaderLayout;
private LinearLayout mFooterLayout;
//empty
private FrameLayout mEmptyView;
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;
@IntDef({ALPHAIN, SCALEIN, SLIDEIN_BOTTOM, SLIDEIN_LEFT, SLIDEIN_RIGHT})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {
}
/**
* 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;
public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener) {
this.mRequestLoadMoreListener = requestLoadMoreListener;
mNextLoadEnable = true;
mLoadMoreEnable = true;
mLoading = false;
}
/**
* Set custom load more
*
* @param loadingView
*/
public void setLoadMoreView(LoadMoreView loadingView) {
this.mLoadMoreView = loadingView;
}
/**
* Load more view count
*
* @return 0 or 1
*/
private 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 recyclerView RecyclerView which use this adapter
* @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(RecyclerView recyclerView, int layoutResId, List<T> data) {
this.mData = data == null ? new ArrayList<T>() : data;
if (layoutResId != 0) {
this.mLayoutResId = layoutResId;
}
/**
* 在适配器中声明一个全局的boolean变量用来保存此刻是否在滚动,然后通过给RecyclerView设置滚动监听,然后在滚动监听器的onScrollStateChanged()方法中给boolean值赋值,看是否在滚动。
* 在使用这个适配器的时候,可以根据滚动状态的不同来判断:比如正在滚动的时候就只显示内存缓存的图片,如果内存缓存中没有就显示一张默认图片;而如果没有在滚动就采用正常的图片加载方案去加载网络或者缓存中的图片。
*/
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
isScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
}
});
/**
* 关闭默认viewholder item动画
*/
RecyclerViewUtil.changeItemAnimation(recyclerView, false);
}
public BaseQuickAdapter(RecyclerView recyclerView, List<T> data) {
this(recyclerView, 0, data);
}
/**
* remove the item associated with the specified position of adapter
*
* @param position
*/
public void remove(int position) {
final T item = mData.get(position);
mData.remove(position);
notifyItemRemoved(position + getHeaderLayoutCount());
onRemove(item);
}
protected void onRemove(T item) {
}
/**
* insert a item associated with the specified position of adapter
*
* @param position
* @param item
*/
public void add(int position, T item) {
mData.add(position, item);
notifyItemInserted(position + getHeaderLayoutCount());
}
/**
* 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();
}
/**
* clear data before reload
*/
public void clearData() {
this.mData.clear();
if (mRequestLoadMoreListener != null) {
mNextLoadEnable = true;
mLoading = false;
mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT);
}
mLastPosition = -1;
notifyDataSetChanged();
}
/**
* add one new data in to certain location
*
* @param position
*/
public void addData(int position, T data) {
if (0 <= position && position < mData.size()) {
mData.add(position, data);
notifyItemInserted(position + getHeaderLayoutCount());
} else {
throw new ArrayIndexOutOfBoundsException("inserted position most greater than 0 and less than data size");
}
}
/**
* add one new data
*/
public void addData(T data) {
mData.add(data);
notifyItemInserted(mData.size() + getHeaderLayoutCount());
}
/**
* add new data in to certain location
*
* @param position
*/
public void addData(int position, List<T> data) {
if (0 <= position && position < mData.size()) {
mData.addAll(position, data);
notifyItemRangeInserted(position + getHeaderLayoutCount(), data.size());
} else {
throw new ArrayIndexOutOfBoundsException("inserted position most greater than 0 and less than data size");
}
}
/**
* additional data;
*
* @param newData
*/
public void addData(List<T> newData) {
this.mData.addAll(newData);
notifyItemRangeInserted(mData.size() - newData.size() + getHeaderLayoutCount(), newData.size());
}
/**
* 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) {
return mData.get(position);
}
/**
* 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 mEmptyView will be return 1 or not will be return 0
*
* @return
*/
public int getEmptyViewCount() {
if (mEmptyView == null || mEmptyView.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(mEmptyView);
break;
case FOOTER_VIEW:
baseViewHolder = createBaseViewHolder(mFooterLayout);
break;
default:
baseViewHolder = onCreateDefViewHolder(parent, viewType);
}
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 boolean flag = true;
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) {
final int viewType = holder.getItemViewType();
final int position = holder.getLayoutPosition() - getHeaderLayoutCount();
switch (viewType) {
case LOADING_VIEW:
mLoadMoreView.convert(holder);
break;
case HEADER_VIEW:
break;
case EMPTY_VIEW:
break;
case FOOTER_VIEW:
break;
default:
convert(holder, mData.get(position), position, isScrolling);
break;
}
}
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) {
return (K) new BaseViewHolder(view);
}
/**
* 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 void addHeaderView(View header) {
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 void addHeaderView(View header, int index) {
addHeaderView(header, index, LinearLayout.VERTICAL);
}
/**
* @param header
* @param index
* @param orientation
*/
public void 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));
}
}
index = index >= mHeaderLayout.getChildCount() ? -1 : index;
mHeaderLayout.addView(header, index);
if (mHeaderLayout.getChildCount() == 1) {
int position = getHeaderViewPosition();
if (position != -1) {
notifyItemInserted(position);
}
}
}
/**
* Append footer to the rear of the mFooterLayout.
*
* @param footer
*/
public void addFooterView(View footer) {
addFooterView(footer, -1);
}
/**
* 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 void addFooterView(View footer, int index) {
if (mFooterLayout == null) {
mFooterLayout = new LinearLayout(footer.getContext());
mFooterLayout.setOrientation(LinearLayout.VERTICAL);
mFooterLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
}
index = index >= mFooterLayout.getChildCount() ? -1 : index;
mFooterLayout.addView(footer, index);
if (mFooterLayout.getChildCount() == 1) {
int position = getFooterViewPosition();
if (position != -1) {
notifyItemInserted(position);
}
}
}
/**
* 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(View emptyView) {
boolean insert = false;
if (mEmptyView == null) {
mEmptyView = new FrameLayout(emptyView.getContext());
mEmptyView.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
insert = true;
}
mEmptyView.removeAllViews();
mEmptyView.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 mEmptyView;
}
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;
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;
}
/**
* To close the animation when loading
*/
public void closeLoadAnimation() {
this.mOpenAnimationEnable = false;
this.mSelectAnimation = null;
this.mCustomAnimation = null;
this.mDuration = 0;
}
/**
* {@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.
* @param position the item position
* @param isScrolling RecyclerView is scrolling
*/
protected abstract void convert(K helper, T item, int position, boolean isScrolling);
/**
* 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;
}
private 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;
}
public boolean isFirstDataItem(int dataItemPosition) {
return dataItemPosition == 0;
}
public boolean isLastDataItem(int dataItemPosition) {
return dataItemPosition == mData.size() - 1;
}
public int getViewHolderPosition(int dataItemPosition) {
return getHeaderLayoutCount() + dataItemPosition;
}
public int getBottomDataPosition() {
return getHeaderLayoutCount() + mData.size() - 1;
}
}