RecyclerView是一个比ListView更加灵活的View,它通过设置LayoutManager来布局子item,常见的LayoutManager有LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager(交错布局)。那么我们应该怎样来实现通用的RecyclerView上拉加载与下拉刷新呢?
在看实现思路之前先看看最终的效果图:第一次制作gif图哈,效果有点问题,里面的所有图片都来自gank.io,还有我自己的练习项目Gank.IO正在开发中欢迎start和给我提一些建议。这个是我的git地址:https://github.com/xiaolutang/GankIo.git。项目现在十分的low希望以后有机会让它变得更加好吧。
扯了那么多的蛋终于要说到实现思路了,首先我们通过重写RecyclerView的Adapter类未recyclerView添加头部和尾部。如何添加Apdater的思想我就不自己写了,这里我参考学习了鸿洋大神的一片文章有兴趣可以自己看看Android 优雅的为RecyclerView添加HeaderView和FooterView地址是https://blog.csdn.net/lmj623565791/article/details/51854533。在下拉的时候通过设置顶部view的marginTop来实现下拉效果。同理尾部通过改变尾部view的marginBottom来实现效果。
IRefreshListener:这几接口定义了RecyclerView加载更多和刷新方法。当下拉刷新和上拉加载的时候回自动调用,
IPullRefresh:这个接口定义了v头部和尾部添加的view的状态更改和变化。
AbsPullRefreshView:是一个抽象类,实现了IPullRefresh接口
CommonPullRefreshView: 通用的头部和尾部
PullRefreshRecyclerView:能够实现上拉加载和下拉刷新的RecyclerView
PullRefreshRecyclerView:复写setAdapter方法,在AdapterWrapter中保存实际的adapter,RecyclerView的adapter实际上是AdapterWrapter对象
public class PullRefreshRecyclerView extends RecyclerView {
private final String TAG = PullRefreshRecyclerView.class.getSimpleName();
/**
* 阻尼效果
*/
private final float OFFSET_RADIO = 1.5f;
/**
* 是否可以下拉 默认可以
*/
private boolean mEnablePullRefresh = true;
private Context context;
private AbsPullRefreshView mHeader;
private AbsPullRefreshView mFooter;
private AdapterWrapper adapterWrapper;
private OnPullRefreshListener listener;
public PullRefreshRecyclerView(Context context) {
this( context,null );
}
public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
this( context, attrs, 0);
}
public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super( context, attrs, defStyle );
this.context = context;
}
private void init(){
mHeader = new CommonPullRefreshView(context,this,AbsPullRefreshView.VIEW_TYPE_HEADER);
mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
mFooter = new CommonPullRefreshView( context,this,AbsPullRefreshView.VIEW_TYPE_FOOTER );
adapterWrapper.AddHeaderView( mHeader.getView( context,this ) );
adapterWrapper.addFootView( mFooter.getView( context,this ) );
}
@Override
public void setAdapter(Adapter adapter) {
adapterWrapper = new AdapterWrapper( adapter );
adapter.registerAdapterDataObserver( new AdapterDataObserver() {
@Override
public void onChanged() {
adapterWrapper.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
adapterWrapper.notifyItemRangeChanged( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
adapterWrapper.notifyItemRangeChanged( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
adapterWrapper.notifyItemRangeInserted( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
adapterWrapper.notifyItemRangeRemoved( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
adapterWrapper.notifyDataSetChanged();
}
} );
init();
super.setAdapter( adapterWrapper );
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent( ev );
}
int mLastRawX, mLastRawY;
@Override
public boolean onTouchEvent(MotionEvent e) {
if(!mEnablePullRefresh){
return super.onTouchEvent( e );
}
int rawX = (int) e.getRawX();
int rawY = (int) e.getRawY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
mLastRawX = (int) e.getRawX();
mLastRawY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int offsetY = (int) (e.getRawY() - mLastRawY);
Log.e( TAG,"onTouchEvent offsetY = "+offsetY +" isSlideToTop "+isSlideToTop() +" isSlideToBottom "+isSlideToBottom());
if(isSlideToTop() && offsetY>0){
mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_PULL );
mHeader.setViewMarginTop( (int) (offsetY / OFFSET_RADIO) );
}else if(isSlideToBottom() && offsetY<0){
mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_PULL );
mFooter.setViewMarginBottom( (int) -(offsetY / OFFSET_RADIO) );
}
// mLastRawX = rawX;
// mLastRawY = rawY;
break;
case MotionEvent.ACTION_UP:
mLastRawX = -1;
mLastRawY = -1;
if(mFooter.getViewState() == AbsPullRefreshView.VIEW_STATE_PULL){
mFooter.setViewMarginBottom( 0 );
if(listener!=null){
mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
listener.loadMore();
}
}else if(mHeader.getViewState() == AbsPullRefreshView.VIEW_STATE_PULL){
mHeader.setViewMarginTop( 0 );
if(listener!=null){
mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
listener.onRefresh();
}
}
break;
}
return super.onTouchEvent( e );
}
private boolean isSlideToTop(){
LayoutManager layoutManager = getLayoutManager();
if(layoutManager instanceof StaggeredGridLayoutManager){
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int firstCompletelyVisibleItemPositions[] = new int[staggeredGridLayoutManager.getSpanCount()];
firstCompletelyVisibleItemPositions = staggeredGridLayoutManager.findFirstCompletelyVisibleItemPositions( firstCompletelyVisibleItemPositions );
if(firstCompletelyVisibleItemPositions[0] == 0 || firstCompletelyVisibleItemPositions[0] == 1){
return true;
}
}
if(layoutManager instanceof LinearLayoutManager){
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
int position = linearLayoutManager.findFirstVisibleItemPosition();
if(position <= 1){
return true;
}else {
return false;
}
}
return !canScrollVertically(1);
}
private boolean isSlideToBottom() {
LayoutManager layoutManager = getLayoutManager();
if(layoutManager instanceof StaggeredGridLayoutManager){
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int spanCount = staggeredGridLayoutManager.getSpanCount();
int lastCompletelyVisibleItemPositions[] = new int[spanCount];
lastCompletelyVisibleItemPositions = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(lastCompletelyVisibleItemPositions );
for (int i=0; i= adapterWrapper.getRealItemCount()-staggeredGridLayoutManager.getSpanCount() || !canScrollVertically(-1)){
Log.e( TAG,"isBottom" );
return true;
}
}
return false;
}
if(layoutManager instanceof LinearLayoutManager){
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
int position = linearLayoutManager.findLastCompletelyVisibleItemPosition();
if(position >= adapterWrapper.getItemCount()-2){
return true;
}else {
return false;
}
}
Log.e( TAG,"canScrollVertically "+canScrollVertically(-1) );
return !canScrollVertically(-1);
}
public void setOnPullRefreshListener(OnPullRefreshListener listener){
this.listener = listener;
}
public void setRefreshFinish(){
mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_NORMAL );
}
public void setLoadMoreFinish(){
mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_NORMAL);
}
private class AdapterWrapper extends RecyclerView.Adapter{
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;
//头集合 尾结合
private SparseArrayCompat mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat mFootViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public AdapterWrapper(Adapter mInnerAdapter) {
this.mInnerAdapter = mInnerAdapter;
}
private boolean isHeaderViewPos(int position){
return position < mHeaderViews.size();
}
public void AddHeaderView(View view){
mHeaderViews.put( mHeaderViews.size()+BASE_ITEM_TYPE_HEADER,view );
}
private boolean isFooterViewPos(int position){
return position >= mHeaderViews.size()+mInnerAdapter.getItemCount();
}
public int getHeadersCount(){
return mHeaderViews.size();
}
public int getFootersCount(){
return mFootViews.size();
}
public void addFootView(View view){
mFootViews.put( mFootViews.size()+BASE_ITEM_TYPE_FOOTER,view );
}
public int getRealItemCount(){
return mInnerAdapter.getItemCount();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(mHeaderViews.get( viewType ) != null){
return new HeaderViewHolder(mHeaderViews.get( viewType ) );
}else if(mFootViews.get( viewType ) != null){
return new FootViewHolder( mFootViews.get( viewType ) );
}
return mInnerAdapter.onCreateViewHolder( parent,viewType );
}
@Override
public int getItemViewType(int position) {
if(isHeaderViewPos( position )){
return mHeaderViews.keyAt( position );
}else if(isFooterViewPos( position )){
return mFootViews.keyAt( position - mHeaderViews.size() - mInnerAdapter.getItemCount() );
}
return mInnerAdapter.getItemViewType(position - mHeaderViews.size());
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (isHeaderViewPos(position)) {
return;
}
if (isFooterViewPos(position)) {
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
@Override
public int getItemCount() {
return mFootViews.size()+mHeaderViews.size()+mInnerAdapter.getItemCount();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
// mInnerAdapter.onAttachedToRecyclerView(recyclerView);
/**
* 解决网格布局问题
*/
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
} else {
return 1;
}
}
});
}
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
if(lp != null && lp instanceof GridLayoutManager.LayoutParams){
GridLayoutManager.LayoutParams p = (GridLayoutManager.LayoutParams) lp;
}
}
}
private class HeaderViewHolder extends RecyclerView.ViewHolder {
HeaderViewHolder(View itemView) {
super(itemView);
}
}
private class FootViewHolder extends RecyclerView.ViewHolder {
FootViewHolder(View itemView) {
super(itemView);
}
}
}
public interface OnPullRefreshListener {
void onRefresh();
void loadMore();
}
}
public interface IPullRefresh {
int viewState = -1;
View getView(Context context, ViewGroup parent);
void onPull(int currentHeight, int refreshHeight, int state);
void onRunning();
void onStopRunning();
}
public abstract class AbsPullRefreshView implements IPullRefresh {
public static final int VIEW_TYPE_HEADER = 0;
public static final int VIEW_TYPE_FOOTER = 1;
/**
* view的类型,头
* */
protected int viewType = VIEW_TYPE_HEADER;
/**
* 未被初始化
* */
protected static final int VIEW_STATE_UNINIT = -1;
protected static final int VIEW_STATE_NORMAL = 0;
protected static final int VIEW_STATE_PULL = 1;
protected static final int VIEW_STATE_RELEASE = 2;
protected static final int VIEW_STATE_RUNNING = 3;
private int viewState = VIEW_STATE_NORMAL;
public AbsPullRefreshView(int viewType) {
this.viewType = viewType;
}
public abstract void setViewMarginTop(int marginTop);
public abstract void setViewMarginBottom(int marginBottom);
protected void updateViewState(int viewState){
this.viewState = viewState;
}
public int getViewState(){
return viewState;
}
}
//这个是我自己设计的通用头部和尾部,如果有其他的效果可以继承AbsPullRefreshView自己来实现,
public class CommonPullRefreshView extends AbsPullRefreshView {
private final String TAG = CommonPullRefreshView.class.getSimpleName();
/**
*根布局
*/
private View mContainer;
private ImageView mImageView;
private TextView mTitleTextView;
private Animation rotateAnimation;
public CommonPullRefreshView(int viewType) {
super( viewType );
}
public CommonPullRefreshView(Context context, ViewGroup parent, int viewType) {
super( viewType );
getView( context,parent );
}
@Override
public View getView(Context context, ViewGroup parent) {
if(mContainer != null){
return mContainer;
}
mContainer = LayoutInflater.from( context ).inflate( R.layout.pull_refresh_header, parent,false);
mImageView = mContainer.findViewById( R.id.pull_refresh_header_ImageView );
mTitleTextView = mContainer.findViewById( R.id.pull_refresh_header_title_TextView );
rotateAnimation = AnimationUtils.loadAnimation( context,R.anim.refreshing_rotate );
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
updateViewState(VIEW_STATE_NORMAL);
return mContainer;
}
@Override
public void onPull(int currentHeight, int refreshHeight, int state) {
mImageView.setVisibility( View.VISIBLE );
switch (this.viewType){
case VIEW_TYPE_HEADER:
mImageView.setImageResource( R.drawable.ic_up_48 );
mTitleTextView.setText( "释放刷新数据" );
break;
case VIEW_TYPE_FOOTER:
mImageView.setImageResource( R.drawable.ic_down_48 );
mTitleTextView.setText( "释放加载更多" );
}
}
@Override
public void onRunning() {
updateViewState(VIEW_STATE_RUNNING);
}
@Override
public void onStopRunning() {
mImageView.setVisibility( View.GONE );
updateViewState(VIEW_STATE_NORMAL);
}
@Override
public void setViewMarginTop(int marginTop) {
if(mContainer == null){
throw new NullPointerException( "mContainer is null in setViewMarginTop do you have call getView" );
}
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams();
marginLayoutParams.topMargin = marginTop;
mContainer.setLayoutParams( marginLayoutParams );
}
@Override
public void setViewMarginBottom(int marginBottom) {
if(mContainer == null){
throw new NullPointerException( "mContainer is null in setViewMarginBottom do you have call getView" );
}
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams();
marginLayoutParams.bottomMargin = marginBottom;
mContainer.setLayoutParams( marginLayoutParams );
}
@Override
protected void updateViewState(int viewState) {
super.updateViewState( viewState );
switch (viewState){
case VIEW_STATE_NORMAL:
if(viewType == VIEW_TYPE_HEADER){
mTitleTextView.setText( "下拉刷新" );
mImageView.clearAnimation();
mImageView.setImageResource( R.drawable.ic_up_48 );
}else if(viewType == VIEW_TYPE_FOOTER){
mTitleTextView.setText( "上拉加载更多" );
mImageView.clearAnimation();
mImageView.setImageResource( R.drawable.ic_down_48);
}
mContainer.getLayoutParams().height = 0;
mContainer.requestLayout();
mContainer.setVisibility( View.GONE);
break;
case VIEW_STATE_PULL:
case VIEW_STATE_RELEASE:
if(viewType == VIEW_TYPE_HEADER){
mTitleTextView.setText( "松开刷新" );
mImageView.clearAnimation();
mImageView.setImageResource( R.drawable.ic_up_48 );
}else if(viewType == VIEW_TYPE_FOOTER){
mTitleTextView.setText( "松开加载" );
mImageView.clearAnimation();
mImageView.setImageResource( R.drawable.ic_down_48);
}
mContainer.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
mContainer.requestLayout();
mContainer.setVisibility( View.VISIBLE);
break;
case VIEW_STATE_RUNNING:
mTitleTextView.setText( "正在加载。。。" );
mImageView.setImageResource( R.drawable.ic_refreshing_48 );
mImageView.setAnimation( rotateAnimation );
mImageView.startAnimation( rotateAnimation );
mContainer.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
mContainer.requestLayout();
mContainer.setVisibility( View.VISIBLE);
break;
}
}
}
通用的布局:
这样就实现了基本实现了通用的添加头部和底部。具体效果可以在这个里面查看:
https://github.com/xiaolutang/androidTool.git