XRecyclerView.java
package com.jcodecraeer.xrecyclerview;
import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import java.util.ArrayList; import java.util.List; import static com.jcodecraeer.xrecyclerview.BaseRefreshHeader.STATE_DONE; public class XRecyclerView extends RecyclerView { /// public boolean isLoading; public boolean isLastRow; public boolean isScrolling = false; public boolean mTrueForShow = true; public boolean touchFlag; /// private boolean isLoadingData = false; private boolean isNoMore = false; private int mRefreshProgressStyle = ProgressStyle.SysProgress; private int mLoadingMoreProgressStyle = ProgressStyle.SysProgress; private ArrayList<View> mHeaderViews = new ArrayList<>(); private WrapAdapter mWrapAdapter; private float mLastY = -1; private static final float DRAG_RATE = 3; private CustomFooterViewCallBack footerViewCallBack; private LoadingListener mLoadingListener; private ArrowRefreshHeader mRefreshHeader; private boolean pullRefreshEnabled = false; private boolean loadingMoreEnabled = false; //下面的ItemViewType是保留值(ReservedItemViewType),如果用户的adapter与它们重复将会强制抛出异常。不过为了简化,我们检测到重复时对用户的提示是ItemViewType必须小于10000 private static final int TYPE_REFRESH_HEADER = 10000;//设置一个很大的数字,尽可能避免和用户的adapter冲突 private static final int TYPE_FOOTER = 10001; private static final int HEADER_INIT_INDEX = 10002; private static List<Integer> sHeaderTypes = new ArrayList<>();//每个header必须有不同的type,不然滚动的时候顺序会变化 //adapter没有数据的时候显示,类似于listView的emptyView private View mEmptyView; private View mFootView; private final RecyclerView.AdapterDataObserver mDataObserver = new DataObserver(); private AppBarStateChangeListener.State appbarState = AppBarStateChangeListener.State.EXPANDED; // limit number to call load more // 控制多出多少条的时候调用 onLoadMore private int limitNumberToCallLoadMore = 1; public XRecyclerView(Context context) { this(context, null); } public XRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { LoadingMoreFooter footView = new LoadingMoreFooter(getContext()); footView.setProgressStyle(mLoadingMoreProgressStyle); mFootView = footView; mFootView.setVisibility(GONE); } /** * call it when you finish the activity, * when you call this,better don't call some kind of functions like * RefreshHeader,because the reference of mHeaderViews is NULL. */ public void destroy() { if (mHeaderViews != null) { mHeaderViews.clear(); mHeaderViews = null; } if (mFootView != null && mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).destroy(); mFootView = null; } if (mRefreshHeader != null) { mRefreshHeader.destroy(); mRefreshHeader = null; } } public ArrowRefreshHeader getDefaultRefreshHeaderView() { if (mRefreshHeader == null) { return null; } return mRefreshHeader; } public LoadingMoreFooter getDefaultFootView() { if (mFootView == null) { return null; } if (mFootView instanceof LoadingMoreFooter) { return ((LoadingMoreFooter) mFootView); } return null; } // set the number to control call load more,see the demo on linearActivity public void setLimitNumberToCallLoadMore(int limitNumberToCallLoadMore) { this.limitNumberToCallLoadMore = limitNumberToCallLoadMore; } public View getFootView() { return mFootView; } public void setFootViewText(String loading, String noMore) { if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setLoadingHint(loading); ((LoadingMoreFooter) mFootView).setNoMoreHint(noMore); } } public void addHeaderView(View view) { if (mHeaderViews == null || sHeaderTypes == null) return; sHeaderTypes.add(HEADER_INIT_INDEX + mHeaderViews.size()); mHeaderViews.add(view); if (mWrapAdapter != null) { mWrapAdapter.notifyDataSetChanged(); } } //根据header的ViewType判断是哪个header private View getHeaderViewByType(int itemType) { if (!isHeaderType(itemType)) { return null; } if (mHeaderViews == null) return null; return mHeaderViews.get(itemType - HEADER_INIT_INDEX); } //判断一个type是否为HeaderType private boolean isHeaderType(int itemViewType) { if (mHeaderViews == null || sHeaderTypes == null) return false; return mHeaderViews.size() > 0 && sHeaderTypes.contains(itemViewType); } //判断是否是XRecyclerView保留的itemViewType private boolean isReservedItemViewType(int itemViewType) { if (itemViewType == TYPE_REFRESH_HEADER || itemViewType == TYPE_FOOTER || sHeaderTypes.contains(itemViewType)) { return true; } else { return false; } } @SuppressWarnings("all") public void setFootView(@NonNull final View view, @NonNull CustomFooterViewCallBack footerViewCallBack) { if (view == null || footerViewCallBack == null) { return; } mFootView = view; this.footerViewCallBack = footerViewCallBack; } public void loadMoreComplete() { isLoadingData = false; if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_COMPLETE); } else { if (footerViewCallBack != null) { footerViewCallBack.onLoadMoreComplete(mFootView); } } } public void setNoMore(boolean noMore) { isLoadingData = false; isNoMore = noMore; if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setState(isNoMore ? LoadingMoreFooter.STATE_NOMORE : LoadingMoreFooter.STATE_COMPLETE); } else { if (footerViewCallBack != null) { footerViewCallBack.onSetNoMore(mFootView, noMore); } } } public void refresh() { if (pullRefreshEnabled && mLoadingListener != null) { mRefreshHeader.setState(ArrowRefreshHeader.STATE_REFRESHING); mLoadingListener.onStartRefresh(); } } public void reset() { setNoMore(false); loadMoreComplete(); refreshComplete(); } public void refreshComplete() { if (mRefreshHeader != null) mRefreshHeader.refreshComplete(); setNoMore(false); } public void setRefreshHeader(ArrowRefreshHeader refreshHeader) { mRefreshHeader = refreshHeader; } public void setPullRefreshEnabled(boolean enabled) { pullRefreshEnabled = enabled; if (pullRefreshEnabled) { mRefreshHeader = new ArrowRefreshHeader(getContext()); mRefreshHeader.setProgressStyle(mRefreshProgressStyle); } } public void setLoadingMoreEnabled(boolean enabled) { loadingMoreEnabled = enabled; if (!enabled) { if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_COMPLETE); } } } public void setRefreshProgressStyle(int style) { mRefreshProgressStyle = style; if (mRefreshHeader != null) { mRefreshHeader.setProgressStyle(style); } } public void setLoadingMoreProgressStyle(int style) { mLoadingMoreProgressStyle = style; if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setProgressStyle(style); } } public void setArrowImageView(int resId) { if (mRefreshHeader != null) { mRefreshHeader.setArrowImageView(resId); } } // if you can't sure that you are 100% going to // have no data load back from server anymore,do not use this @Deprecated public void setEmptyView(View emptyView) { this.mEmptyView = emptyView; mDataObserver.onChanged(); } public View getEmptyView() { return mEmptyView; } @Override public void setAdapter(Adapter adapter) { mWrapAdapter = new WrapAdapter(adapter); super.setAdapter(mWrapAdapter); adapter.registerAdapterDataObserver(mDataObserver); mDataObserver.onChanged(); } //避免用户自己调用getAdapter() 引起的ClassCastException @Override public Adapter getAdapter() { if (mWrapAdapter != null) return mWrapAdapter.getOriginalAdapter(); else return null; } SpanSizeLookupListener mSpanSizeLookupListener; public interface SpanSizeLookupListener { public int getSpanCount(int position); } public void setLayoutManager(GridLayoutManager layout, SpanSizeLookupListener listener) { mSpanSizeLookupListener = listener; setLayoutManager(layout); } @Override public void setLayoutManager(LayoutManager layout) { super.setLayoutManager(layout); if (mWrapAdapter != null) { if (layout instanceof GridLayoutManager) { if (mSpanSizeLookupListener == null) { throw new RuntimeException("please use setLayoutManager(GridLayoutManager layout, SpanSizeLookupListener listener) instead of setLayoutManager(LayoutManager layout)"); } final GridLayoutManager gridManager = ((GridLayoutManager) layout); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int adjPosition = position - (mHeaderViews.size() + (pullRefreshEnabled ? 1 : 0)); return (mWrapAdapter.isHeader(position) || mWrapAdapter.isFooter(position) || mWrapAdapter.isRefreshHeader(position)) ? gridManager.getSpanCount() : (mSpanSizeLookupListener != null ? mSpanSizeLookupListener.getSpanCount(adjPosition) : 1); } }); } } else { throw new RuntimeException("please setAdapter first"); } } /** * ===================== try to adjust the position for XR when you call those functions below ====================== */ // which cause "Called attach on a child which is not detached" exception info. // {reason analyze @link:http://www.cnblogs.com/linguanh/p/5348510.html} // by lgh on 2017-11-13 23:55 // example: listData.remove(position); You can also see a demo on LinearActivity public <T> void notifyItemRemoved(List<T> listData, int position) { if (mWrapAdapter.adapter == null) return; int headerSize = getHeaders_includingRefreshCount(); int adjPos = position + headerSize; mWrapAdapter.adapter.notifyItemRemoved(adjPos); mWrapAdapter.adapter.notifyItemRangeChanged(headerSize, listData.size(), new Object()); } public <T> void notifyItemInserted(List<T> listData, int position) { if (mWrapAdapter.adapter == null) return; int headerSize = getHeaders_includingRefreshCount(); int adjPos = position + headerSize; mWrapAdapter.adapter.notifyItemInserted(adjPos); mWrapAdapter.adapter.notifyItemRangeChanged(headerSize, listData.size(), new Object()); } public void notifyItemChanged(int position) { if (mWrapAdapter.adapter == null) return; int adjPos = position + getHeaders_includingRefreshCount(); mWrapAdapter.adapter.notifyItemChanged(adjPos); } public void notifyItemChanged(int position, Object o) { if (mWrapAdapter.adapter == null) return; int adjPos = position + getHeaders_includingRefreshCount(); mWrapAdapter.adapter.notifyItemChanged(adjPos, o); } private int getHeaders_includingRefreshCount() { return mWrapAdapter.getHeadersCount() + (pullRefreshEnabled ? 1 : 0); } /** * ======================================================= end ======================================================= */ @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) { LayoutManager layoutManager = getLayoutManager(); int lastVisibleItemPosition; if (layoutManager instanceof GridLayoutManager) { lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into); lastVisibleItemPosition = findMax(into); } else { lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } int adjAdapterItemCount = layoutManager.getItemCount() + getHeaders_includingRefreshCount(); Log.e("aaaaa", "adjAdapterItemCount " + adjAdapterItemCount + " getItemCount " + layoutManager.getItemCount()); int status = STATE_DONE; if (mRefreshHeader != null) status = mRefreshHeader.getState(); if ( layoutManager.getChildCount() > 0 && lastVisibleItemPosition >= adjAdapterItemCount - limitNumberToCallLoadMore && adjAdapterItemCount >= layoutManager.getChildCount() && !isNoMore && status < ArrowRefreshHeader.STATE_REFRESHING ) { isLoadingData = true; if (mFootView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_LOADING); } else { if (footerViewCallBack != null) { footerViewCallBack.onLoadingMore(mFootView); } } mLoadingListener.onLoadMore(); } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); if (isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) { if (mRefreshHeader == null) break; mRefreshHeader.onMove(deltaY / DRAG_RATE); if (mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) { return false; } } break; default: mLastY = -1; // reset if (isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) { if (mRefreshHeader != null && mRefreshHeader.releaseAction()) { if (mLoadingListener != null) { mLoadingListener.onStartRefresh(); } } } break; } return super.onTouchEvent(ev); } private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max; } private boolean isOnTop() { if (mRefreshHeader == null) return false; if (mRefreshHeader.getParent() != null) { return true; } else { return false; } } private class DataObserver extends RecyclerView.AdapterDataObserver { @Override public void onChanged() { if (mWrapAdapter != null) { mWrapAdapter.notifyDataSetChanged(); } if (mWrapAdapter != null && mEmptyView != null) { int emptyCount = (pullRefreshEnabled ? 1 : 0) + mWrapAdapter.getHeadersCount(); if (loadingMoreEnabled) { emptyCount++; } if (mWrapAdapter.getItemCount() == emptyCount) { mEmptyView.setVisibility(View.VISIBLE); XRecyclerView.this.setVisibility(View.GONE); } else { mEmptyView.setVisibility(View.GONE); XRecyclerView.this.setVisibility(View.VISIBLE); } } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { mWrapAdapter.notifyItemMoved(fromPosition, toPosition); } } ; private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> { private RecyclerView.Adapter adapter; public WrapAdapter(RecyclerView.Adapter adapter) { this.adapter = adapter; } public RecyclerView.Adapter getOriginalAdapter() { return this.adapter; } public boolean isHeader(int position) { if (mHeaderViews == null) return false; return position < (mHeaderViews.size() + (pullRefreshEnabled ? 1 : 0)); } public boolean isFooter(int position) { if (loadingMoreEnabled) { return position == getItemCount() - 1; } else { return false; } } public boolean isRefreshHeader(int position) { return (pullRefreshEnabled && position == 0); } public int getHeadersCount() { if (mHeaderViews == null) return 0; return mHeaderViews.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_REFRESH_HEADER) { return new SimpleViewHolder(mRefreshHeader); } else if (isHeaderType(viewType)) { return new SimpleViewHolder(getHeaderViewByType(viewType)); } else if (viewType == TYPE_FOOTER) { return new SimpleViewHolder(mFootView); } return adapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeader(position) || isRefreshHeader(position)) { return; } int adjPosition = position - (getHeadersCount() + (pullRefreshEnabled ? 1 : 0)); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition >= 0 && adjPosition < adapterCount) { adapter.onBindViewHolder(holder, adjPosition); } } } // some times we need to override this @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) { if (isHeader(position) || isRefreshHeader(position)) { return; } int adjPosition = position - (getHeadersCount() + (pullRefreshEnabled ? 1 : 0)); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition >= 0 && adjPosition < adapterCount) { if (payloads.isEmpty()) { adapter.onBindViewHolder(holder, adjPosition); } else { adapter.onBindViewHolder(holder, adjPosition, payloads); } } } } @Override public int getItemCount() { int adjLen = 0; if (pullRefreshEnabled) adjLen++; if (loadingMoreEnabled) adjLen++; if (adapter != null) { return getHeadersCount() + adapter.getItemCount() + adjLen; } else { return getHeadersCount() + adjLen; } } @Override public int getItemViewType(int position) { if (isRefreshHeader(position)) { return TYPE_REFRESH_HEADER; } if (isHeader(position)) { position = position - (pullRefreshEnabled ? 1 : 0); return sHeaderTypes.get(position); } if (isFooter(position)) { return TYPE_FOOTER; } if (adapter != null) { int adapterCount; adapterCount = adapter.getItemCount(); int adjPosition = position - (getHeadersCount() + (pullRefreshEnabled ? 1 : 0)); if (adjPosition >= 0 && adjPosition < adapterCount) { int type = adapter.getItemViewType(adjPosition); if (isReservedItemViewType(type)) { throw new IllegalStateException("XRecyclerView require itemViewType in adapter should be less than 10000 "); } return type; } } return 0; } @Override public long getItemId(int position) { if (adapter != null && position >= getHeadersCount() + (pullRefreshEnabled ? 1 : 0)) { int adjPosition = position - (getHeadersCount() + (pullRefreshEnabled ? 1 : 0)); if (adjPosition < adapter.getItemCount()) { return adapter.getItemId(adjPosition); } } return -1; } @Override public void onAttachedToRecyclerView(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) { Log.i("TAG", "setSpanSizeLookup position=" + position); return (isHeader(position) || isFooter(position) || isRefreshHeader(position)) ? gridManager.getSpanCount() : 1; } }); } adapter.onAttachedToRecyclerView(recyclerView); } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { adapter.onDetachedFromRecyclerView(recyclerView); } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); Log.i("TAG", "onViewAttachedToWindow"); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isHeader(holder.getLayoutPosition()) || isRefreshHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } adapter.onViewAttachedToWindow(holder); } @Override public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { adapter.onViewDetachedFromWindow(holder); } @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { adapter.onViewRecycled(holder); } @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { return adapter.onFailedToRecycleView(holder); } @Override public void unregisterAdapterDataObserver(AdapterDataObserver observer) { adapter.unregisterAdapterDataObserver(observer); } @Override public void registerAdapterDataObserver(AdapterDataObserver observer) { adapter.registerAdapterDataObserver(observer); } private class SimpleViewHolder extends RecyclerView.ViewHolder { public SimpleViewHolder(View itemView) { super(itemView); } } } public void setLoadingListener(LoadingListener listener) { mLoadingListener = listener; } public interface LoadingListener { void onStartRefresh(); void onLoadMore(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); //解决和CollapsingToolbarLayout冲突的问题 AppBarLayout appBarLayout = null; ViewParent p = getParent(); while (p != null) { if (p instanceof CoordinatorLayout) { break; } p = p.getParent(); } if (p instanceof CoordinatorLayout) { CoordinatorLayout coordinatorLayout = (CoordinatorLayout) p; final int childCount = coordinatorLayout.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View child = coordinatorLayout.getChildAt(i); if (child instanceof AppBarLayout) { appBarLayout = (AppBarLayout) child; break; } } if (appBarLayout != null) { appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { @Override public void onStateChanged(AppBarLayout appBarLayout, State state) { appbarState = state; } }); } } } public class DividerItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDivider; private int mOrientation; /** * Sole constructor. Takes in a {@link Drawable} to be used as the interior * divider. * * @param divider A divider {@code Drawable} to be drawn on the RecyclerView */ public DividerItemDecoration(Drawable divider) { mDivider = divider; } /** * Draws horizontal or vertical dividers onto the parent RecyclerView. * * @param canvas The {@link Canvas} onto which dividers will be drawn * @param parent The RecyclerView onto which dividers are being added * @param state The current RecyclerView.State of the RecyclerView */ @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { if (mOrientation == LinearLayoutManager.HORIZONTAL) { drawHorizontalDividers(canvas, parent); } else if (mOrientation == LinearLayoutManager.VERTICAL) { drawVerticalDividers(canvas, parent); } } /** * Determines the size and location of offsets between items in the parent * RecyclerView. * * @param outRect The {@link Rect} of offsets to be added around the child * view * @param view The child view to be decorated with an offset * @param parent The RecyclerView onto which dividers are being added * @param state The current RecyclerView.State of the RecyclerView */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (parent.getChildAdapterPosition(view) <= mWrapAdapter.getHeadersCount() + (pullRefreshEnabled ? 1 : 0)) { return; } mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation(); if (mOrientation == LinearLayoutManager.HORIZONTAL) { outRect.left = mDivider.getIntrinsicWidth(); } else if (mOrientation == LinearLayoutManager.VERTICAL) { outRect.top = mDivider.getIntrinsicHeight(); } } /** * Adds dividers to a RecyclerView with a LinearLayoutManager or its * subclass oriented horizontally. * * @param canvas The {@link Canvas} onto which horizontal dividers will be * drawn * @param parent The RecyclerView onto which horizontal dividers are being * added */ private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) { int parentTop = parent.getPaddingTop(); int parentBottom = parent.getHeight() - parent.getPaddingBottom(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount - 1; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int parentLeft = child.getRight() + params.rightMargin; int parentRight = parentLeft + mDivider.getIntrinsicWidth(); mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); mDivider.draw(canvas); } } /** * Adds dividers to a RecyclerView with a LinearLayoutManager or its * subclass oriented vertically. * * @param canvas The {@link Canvas} onto which vertical dividers will be * drawn * @param parent The RecyclerView onto which vertical dividers are being * added */ private void drawVerticalDividers(Canvas canvas, RecyclerView parent) { int parentLeft = parent.getPaddingLeft(); int parentRight = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount - 1; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int parentTop = child.getBottom() + params.bottomMargin; int parentBottom = parentTop + mDivider.getIntrinsicHeight(); mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); mDivider.draw(canvas); } } } /** * add by LinGuanHong below */ private int scrollDyCounter = 0; @Override public void scrollToPosition(int position) { super.scrollToPosition(position); /** if we scroll to position 0, the scrollDyCounter should be reset */ if (position == 0) { scrollDyCounter = 0; } } @Override public void onScrolled(int dx, int dy) { super.onScrolled(dx, dy); if (scrollAlphaChangeListener == null) { return; } int height = scrollAlphaChangeListener.setLimitHeight(); scrollDyCounter = scrollDyCounter + dy; if (scrollDyCounter <= 0) { scrollAlphaChangeListener.onAlphaChange(0); } else if (scrollDyCounter <= height && scrollDyCounter > 0) { float scale = (float) scrollDyCounter / height; /** 255/height = x/255 */ float alpha = (255 * scale); scrollAlphaChangeListener.onAlphaChange((int) alpha); } else { scrollAlphaChangeListener.onAlphaChange(255); } } private ScrollAlphaChangeListener scrollAlphaChangeListener; public void setScrollAlphaChangeListener( ScrollAlphaChangeListener scrollAlphaChangeListener ) { this.scrollAlphaChangeListener = scrollAlphaChangeListener; } public interface ScrollAlphaChangeListener { void onAlphaChange(int alpha); /** * you can handle the alpha insert it */ int setLimitHeight(); /** set a height for the begging of the alpha start to change */ } }
BaseRefreshHeader.java
package com.jcodecraeer.xrecyclerview; /** * Created by jianghejie on 15/11/22. */ interface BaseRefreshHeader { int STATE_NORMAL = 0; int STATE_RELEASE_TO_REFRESH = 1; int STATE_REFRESHING = 2; int STATE_DONE = 3; void onMove(float delta); boolean releaseAction(); void refreshComplete(); }
ArrowRefreshHeader.java
package com.jcodecraeer.xrecyclerview; import android.animation.ValueAnimator; import android.content.Context; import android.content.SharedPreferences; import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.jcodecraeer.xrecyclerview.progressindicator.AVLoadingIndicatorView; import java.util.Date; public class ArrowRefreshHeader extends LinearLayout implements BaseRefreshHeader { private static final String XR_REFRESH_KEY = "XR_REFRESH_KEY"; private static final String XR_REFRESH_TIME_KEY = "XR_REFRESH_TIME_KEY"; private LinearLayout mContainer; private ImageView mArrowImageView; private SimpleViewSwitcher mProgressBar; private TextView mStatusTextView; private int mState = STATE_NORMAL; private TextView mHeaderTimeView; private LinearLayout mHeaderRefreshTimeContainer; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private static final int ROTATE_ANIM_DURATION = 180; public int mMeasuredHeight; private AVLoadingIndicatorView progressView; public void destroy() { mProgressBar = null; if (progressView != null) { progressView.destroy(); progressView = null; } if (mRotateUpAnim != null) { mRotateUpAnim.cancel(); mRotateUpAnim = null; } if (mRotateDownAnim != null) { mRotateDownAnim.cancel(); mRotateDownAnim = null; } } public ArrowRefreshHeader(Context context) { super(context); initView(); } /** * @param context * @param attrs */ public ArrowRefreshHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(); } // public void setRefreshTimeVisible(boolean show){ // if(mHeaderRefreshTimeContainer != null) // mHeaderRefreshTimeContainer.setVisibility(show?VISIBLE:GONE); // } private void initView() { // 初始情况,设置下拉刷新view高度为0 mContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate( R.layout.listview_header_xrecycle, null); // mHeaderRefreshTimeContainer // = (LinearLayout) mContainer.findViewById(R.id.header_refresh_time_container); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); lp.setMargins(0, 0, 0, 0); this.setLayoutParams(lp); this.setPadding(0, 0, 0, 0); addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0)); setGravity(Gravity.BOTTOM); mArrowImageView = (ImageView) findViewById(R.id.listview_header_arrow); mStatusTextView = (TextView) findViewById(R.id.refresh_status_textview); //init the progress view mProgressBar = (SimpleViewSwitcher) findViewById(R.id.listview_header_progressbar); progressView = new AVLoadingIndicatorView(getContext()); progressView.setIndicatorColor(0xffB5B5B5); progressView.setIndicatorId(ProgressStyle.BallSpinFadeLoader); if (mProgressBar != null) mProgressBar.setView(progressView); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); mHeaderTimeView = (TextView) findViewById(R.id.last_refresh_time); measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mMeasuredHeight = getMeasuredHeight(); } public void setProgressStyle(int style) { if (style == ProgressStyle.SysProgress) { if (mProgressBar != null) mProgressBar.setView(new ProgressBar(getContext(), null, android.R.attr.progressBarStyle)); } else { progressView = new AVLoadingIndicatorView(this.getContext()); progressView.setIndicatorColor(0xffB5B5B5); progressView.setIndicatorId(style); mProgressBar.setView(progressView); } } public void setArrowImageView(int resid) { mArrowImageView.setImageResource(resid); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 显示进度 mArrowImageView.clearAnimation(); // mArrowImageView.setVisibility(View.INVISIBLE); if (mProgressBar != null) // mProgressBar.setVisibility(View.VISIBLE); smoothScrollTo(mMeasuredHeight); } else if (state == STATE_DONE) { // mArrowImageView.setVisibility(View.INVISIBLE); if (mProgressBar != null) { // mProgressBar.setVisibility(View.INVISIBLE); } } else { // 显示箭头图片 // mArrowImageView.setVisibility(View.VISIBLE); if (mProgressBar != null) { // mProgressBar.setVisibility(View.INVISIBLE); } } mHeaderTimeView.setText(friendlyTime(getLastRefreshTime())); switch (state) { case STATE_NORMAL: if (mState == STATE_RELEASE_TO_REFRESH) { mArrowImageView.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mArrowImageView.clearAnimation(); } mStatusTextView.setText(R.string.listview_header_hint_normal); break; case STATE_RELEASE_TO_REFRESH: if (mState != STATE_RELEASE_TO_REFRESH) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mStatusTextView.setText(R.string.listview_header_hint_release); } break; case STATE_REFRESHING: mStatusTextView.setText(R.string.refreshing); break; case STATE_DONE: mStatusTextView.setText(R.string.refresh_done); break; default: } mState = state; } public int getState() { return mState; } private long getLastRefreshTime() { SharedPreferences s = getContext() .getSharedPreferences(XR_REFRESH_KEY, Context.MODE_APPEND); return s.getLong(XR_REFRESH_TIME_KEY, new Date().getTime()); } private void saveLastRefreshTime(long refreshTime) { SharedPreferences s = getContext() .getSharedPreferences(XR_REFRESH_KEY, Context.MODE_APPEND); s.edit().putLong(XR_REFRESH_TIME_KEY, refreshTime).commit(); } @Override public void refreshComplete() { mHeaderTimeView.setText(friendlyTime(getLastRefreshTime())); saveLastRefreshTime(System.currentTimeMillis()); setState(STATE_DONE); new Handler().postDelayed(new Runnable() { public void run() { reset(); } }, 200); } public void setVisibleHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer.getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisibleHeight() { LayoutParams lp = (LayoutParams) mContainer.getLayoutParams(); return lp.height; } @Override public void onMove(float delta) { if (getVisibleHeight() > 0 || delta > 0) { setVisibleHeight((int) delta + getVisibleHeight()); if (mState <= STATE_RELEASE_TO_REFRESH) { // 未处于刷新状态,更新箭头 if (getVisibleHeight() > mMeasuredHeight) { setState(STATE_RELEASE_TO_REFRESH); } else { setState(STATE_NORMAL); } } } } @Override public boolean releaseAction() { boolean isOnRefresh = false; int height = getVisibleHeight(); if (height == 0) // not visible. isOnRefresh = false; if (getVisibleHeight() > mMeasuredHeight && mState < STATE_REFRESHING) { setState(STATE_REFRESHING); isOnRefresh = true; } // refreshing and header isn't shown fully. do nothing. if (mState == STATE_REFRESHING && height <= mMeasuredHeight) { //return; } if (mState != STATE_REFRESHING) { smoothScrollTo(0); } if (mState == STATE_REFRESHING) { int destHeight = mMeasuredHeight; smoothScrollTo(destHeight); } return isOnRefresh; } public void reset() { smoothScrollTo(0); new Handler().postDelayed(new Runnable() { public void run() { setState(STATE_NORMAL); } }, 500); } private void smoothScrollTo(int destHeight) { ValueAnimator animator = ValueAnimator.ofInt(getVisibleHeight(), destHeight); animator.setDuration(300).start(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setVisibleHeight((int) animation.getAnimatedValue()); } }); animator.start(); } public static String friendlyTime(Date time) { return friendlyTime(time.getTime()); } public static String friendlyTime(long time) { //获取time距离当前的秒数 int ct = (int) ((System.currentTimeMillis() - time) / 1000); if (ct == 0) { return "刚刚"; } if (ct > 0 && ct < 60) { return ct + "秒前"; } if (ct >= 60 && ct < 3600) { return Math.max(ct / 60, 1) + "分钟前"; } if (ct >= 3600 && ct < 86400) return ct / 3600 + "小时前"; if (ct >= 86400 && ct < 2592000) { //86400 * 30 int day = ct / 86400; return day + "天前"; } if (ct >= 2592000 && ct < 31104000) { //86400 * 30 return ct / 2592000 + "月前"; } return ct / 31104000 + "年前"; } }