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.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.util.Log; 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 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.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; public abstract class BaseFetchLoadAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> implements IRecyclerView { private static final String TAG = BaseFetchLoadAdapter.class.getSimpleName(); // fetch more public interface RequestFetchMoreListener { void onFetchMoreRequested(); } private boolean mFetching = false; private boolean mFetchMoreEnable = false; private boolean mNextFetchEnable = false; private boolean mFirstFetchSuccess = true; private int mAutoFetchMoreSize = 1; // 距离顶部多少条就开始拉取数据了 private RequestFetchMoreListener mRequestFetchMoreListener; private LoadMoreView mFetchMoreView = new SimpleLoadMoreView(); //load more public interface RequestLoadMoreListener { void onLoadMoreRequested(); } private boolean mLoading = false; private boolean mNextLoadEnable = false; private boolean mLoadMoreEnable = false; private boolean mFirstLoadSuccess = true; private int mAutoLoadMoreSize = 1; // 距离底部多少条就开始加载数据了 private RequestLoadMoreListener mRequestLoadMoreListener; private LoadMoreView mLoadMoreView = new SimpleLoadMoreView(); // animation private boolean mAnimationShowFirstOnly = true; private boolean mOpenAnimationEnable = false; private Interpolator mInterpolator = new LinearInterpolator(); private int mAnimationDuration = 200; private int mLastPosition = -1; // @AnimationType private BaseAnimation mCustomAnimation; private BaseAnimation mSelectAnimation = new AlphaInAnimation(); // empty private FrameLayout mEmptyView; private boolean mIsUseEmpty = true; // basic protected Context mContext; protected int mLayoutResId; protected LayoutInflater mLayoutInflater; protected List<T> mData; private boolean isScrolling = false; /** * 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); @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; /** * 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 BaseFetchLoadAdapter(RecyclerView recyclerView, int layoutResId, List<T> data) { this.mData = data == null ? new ArrayList<T>() : data; if (layoutResId != 0) { this.mLayoutResId = layoutResId; } 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); } @Override public int getHeaderLayoutCount() { return getFetchMoreViewCount(); } /** * *********************************** fetch more 顶部下拉加载 *********************************** */ public void setOnFetchMoreListener(RequestFetchMoreListener requestFetchMoreListener) { this.mRequestFetchMoreListener = requestFetchMoreListener; mNextFetchEnable = true; mFetchMoreEnable = true; mFetching = false; } public void setAutoFetchMoreSize(int autoFetchMoreSize) { if (autoFetchMoreSize > 1) { mAutoFetchMoreSize = autoFetchMoreSize; } } public void setFetchMoreView(LoadMoreView fetchMoreView) { this.mFetchMoreView = fetchMoreView; // 自定义View } private int getFetchMoreViewCount() { if (mRequestFetchMoreListener == null || !mFetchMoreEnable) { return 0; } if (!mNextFetchEnable && mFetchMoreView.isLoadEndMoreGone()) { return 0; } return 1; } /** * 列表滑动时自动拉取数据 * * @param position */ private void autoRequestFetchMoreData(int position) { if (getFetchMoreViewCount() == 0) { return; } if (position > mAutoFetchMoreSize - 1) { return; } if (mFetchMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) { return; } if (mData.size() == 0 && mFirstFetchSuccess) { return; // 都还没有数据,不自动触发加载,等外部塞入数据后再加载 } Log.d(TAG, "auto fetch, pos=" + position); mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING); if (!mFetching) { mFetching = true; mRequestFetchMoreListener.onFetchMoreRequested(); } } /** * fetch complete */ public void fetchMoreComplete(final RecyclerView recyclerView, final List<T> newData) { addFrontData(newData); // notifyItemRangeInserted从顶部向下加入View,顶部View并没有改变 if (getFetchMoreViewCount() == 0) { return; } fetchMoreComplete(recyclerView, newData.size()); } public void fetchMoreComplete(final RecyclerView recyclerView, int newDataSize) { if (getFetchMoreViewCount() == 0) { return; } mFetching = false; mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(0); // 定位到insert新消息前的top消息位置。必须移动,否则还在顶部,会继续fetch!!! if (recyclerView != null) { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); // 只有LinearLayoutManager才有查找第一个和最后一个可见view位置的方法 if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager; //获取第一个可见view的位置 int firstItemPosition = linearManager.findFirstVisibleItemPosition(); if (firstItemPosition == 0) { // 最顶部可见的View已经是FetchMoreView了,那么add数据&局部刷新后,要进行定位到上次的最顶部消息。 recyclerView.scrollToPosition(newDataSize + getFetchMoreViewCount()); } } else { recyclerView.scrollToPosition(newDataSize); } } } /** * fetch end, no more data * * @param gone if true gone the fetch more view */ public void fetchMoreEnd(boolean gone) { if (getFetchMoreViewCount() == 0) { return; } mFetching = false; mNextFetchEnable = false; mFetchMoreView.setLoadMoreEndGone(gone); if (gone) { notifyItemRemoved(0); } else { mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END); notifyItemChanged(0); } } /** * fetch failed */ public void fetchMoreFailed() { if (getFetchMoreViewCount() == 0) { return; } mFetching = false; if (mData.size() == 0) { mFirstFetchSuccess = false; // 首次加载失败 } mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_FAIL); notifyItemChanged(0); } /** * *********************************** load more 底部上拉加载 *********************************** */ public void setLoadMoreView(LoadMoreView loadingView) { this.mLoadMoreView = loadingView; // 自定义View } public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener) { this.mRequestLoadMoreListener = requestLoadMoreListener; mNextLoadEnable = true; mLoadMoreEnable = true; mLoading = false; } public void setAutoLoadMoreSize(int autoLoadMoreSize) { if (autoLoadMoreSize > 1) { mAutoLoadMoreSize = autoLoadMoreSize; } } private int getLoadMoreViewCount() { if (mRequestLoadMoreListener == null || !mLoadMoreEnable) { return 0; } if (!mNextLoadEnable && mLoadMoreView.isLoadEndMoreGone()) { return 0; } if (mData.size() == 0) { return 0; } return 1; } /** * 列表滑动时自动加载数据 * * @param position */ private void autoRequestLoadMoreData(int position) { if (getLoadMoreViewCount() == 0) { return; } if (position < getItemCount() - mAutoLoadMoreSize) { return; } if (mLoadMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) { return; } if (mData.size() == 0 && mFirstLoadSuccess) { return; // 都还没有数据,不自动触发加载,等外部塞入数据后再加载 } Log.d(TAG, "auto load, pos=" + position); mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING); if (!mLoading) { mLoading = true; mRequestLoadMoreListener.onLoadMoreRequested(); } } /** * load complete */ public void loadMoreComplete(final List<T> newData) { appendData(newData); loadMoreComplete(); } public void loadMoreComplete() { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(getFetchMoreViewCount() + mData.size()); } /** * load 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(getFetchMoreViewCount() + mData.size()); } else { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END); notifyItemChanged(getFetchMoreViewCount() + mData.size()); } } /** * load failed */ public void loadMoreFail() { if (getLoadMoreViewCount() == 0) { return; } mLoading = false; if (mData.size() == 0) { mFirstLoadSuccess = false; // 首次加载失败 } mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_FAIL); notifyItemChanged(getFetchMoreViewCount() + mData.size()); } /** * 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(getFetchMoreViewCount() + mData.size()); } } else { if (newLoadMoreCount == 1) { mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemInserted(getFetchMoreViewCount() + mData.size()); } } } /** * Returns the enabled status for load more. * * @return True if load more is enabled, false otherwise. */ public boolean isLoadMoreEnable() { return mLoadMoreEnable; } /** * *********************************** 数据源管理 *********************************** */ /** * 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); } if (mRequestFetchMoreListener != null) { mNextFetchEnable = true; mFetchMoreEnable = true; mFetching = false; mFetchMoreView.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); } if (mRequestFetchMoreListener != null) { mNextFetchEnable = true; mFetching = false; mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); } mLastPosition = -1; notifyDataSetChanged(); } /** * 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 + getFetchMoreViewCount()); } /** * 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(getFetchMoreViewCount() + position, data.size()); } else { throw new ArrayIndexOutOfBoundsException("inserted position most greater than 0 and less than data size"); } } /** * 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) { } /** * add new data to head location */ public void addFrontData(List<T> data) { if (data == null || data.isEmpty()) { return; } mData.addAll(0, data); notifyItemRangeInserted(getFetchMoreViewCount(), data.size()); // add到FetchMoreView之下,保持FetchMoreView在顶部 } /** * additional data; * * @param newData */ public void appendData(List<T> newData) { this.mData.addAll(newData); notifyItemRangeInserted(mData.size() - newData.size() + getFetchMoreViewCount(), newData.size()); } public void appendData(T newData) { List<T> data = new ArrayList<>(1); data.add(newData); appendData(data); } /** * 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); } public int getDataSize() { return mData == null ? 0 : mData.size(); } public int getBottomDataPosition() { return getHeaderLayoutCount() + mData.size() - 1; } public void notifyDataItemChanged(int dataIndex) { notifyItemChanged(getHeaderLayoutCount() + dataIndex); } /** * *********************************** ViewHolder/ViewType *********************************** */ @Override public int getItemCount() { int count; if (getEmptyViewCount() == 1) { count = 1; } else { count = getFetchMoreViewCount() + mData.size() + getLoadMoreViewCount(); } return count; } @Override public int getItemViewType(int position) { if (getEmptyViewCount() == 1) { return EMPTY_VIEW; } // fetch autoRequestFetchMoreData(position); // load autoRequestLoadMoreData(position); int fetchMoreCount = getFetchMoreViewCount(); if (position < fetchMoreCount) { Log.d(TAG, "FETCH pos=" + position); return FETCHING_VIEW; } else { int adjPosition = position - fetchMoreCount; int adapterCount = mData.size(); if (adjPosition < adapterCount) { Log.d(TAG, "DATA pos=" + position); return getDefItemViewType(adjPosition); } else { Log.d(TAG, "LOAD pos=" + position); return LOADING_VIEW; } } } /** * 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 LOADING_VIEW: mLoadMoreView.convert(holder); break; case FETCHING_VIEW: mFetchMoreView.convert(holder); break; case EMPTY_VIEW: break; default: convert(holder, mData.get(holder.getLayoutPosition() - getFetchMoreViewCount()), positions, 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)); } /** * @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); } /** * 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); } protected int getDefItemViewType(int position) { return super.getItemViewType(position); } @Override public K onCreateViewHolder(ViewGroup parent, int viewType) { K baseViewHolder; this.mContext = parent.getContext(); this.mLayoutInflater = LayoutInflater.from(mContext); switch (viewType) { case FETCHING_VIEW: baseViewHolder = getFetchingView(parent); break; case LOADING_VIEW: baseViewHolder = getLoadingView(parent); break; case EMPTY_VIEW: baseViewHolder = createBaseViewHolder(mEmptyView); 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(getFetchMoreViewCount() + mData.size()); } } }); return holder; } private K getFetchingView(ViewGroup parent) { View view = getItemView(mFetchMoreView.getLayoutId(), parent); K holder = createBaseViewHolder(view); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mFetchMoreView.getLoadMoreStatus() == LoadMoreView.STATUS_FAIL) { mFetchMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(0); } } }); 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 == LOADING_VIEW || type == FETCHING_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 == LOADING_VIEW || type == FETCHING_VIEW) ? gridManager.getSpanCount() : 1; } else { return (type == EMPTY_VIEW || type == LOADING_VIEW || type == FETCHING_VIEW) ? gridManager .getSpanCount() : mSpanSizeLookup.getSpanSize(gridManager, position - getFetchMoreViewCount()); } } }); } } private SpanSizeLookup mSpanSizeLookup; public interface SpanSizeLookup { int getSpanSize(GridLayoutManager gridLayoutManager, int position); } /** * *********************************** EmptyView *********************************** */ /** * 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; } 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) { notifyItemInserted(0); } } } /** * 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; } /** * *********************************** 动画 *********************************** */ /** * 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.mAnimationDuration = 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 setAnimationShowFirstOnly(boolean firstOnly) { this.mAnimationShowFirstOnly = firstOnly; } /** * Sets the duration of the animation. * * @param duration The length of the animation, in milliseconds. */ public void setAnimationDuration(int duration) { mAnimationDuration = duration; } /** * add animation when you want to show time * * @param holder */ private void addAnimation(RecyclerView.ViewHolder holder) { if (mOpenAnimationEnable) { if (!mAnimationShowFirstOnly || holder.getLayoutPosition() > mLastPosition) { BaseAnimation animation; 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(mAnimationDuration).start(); anim.setInterpolator(mInterpolator); } }