为了优化用户体验,从App的性能和节省用户流量方面考虑,都会涉及到对列表数据进行分页加载,使用ListView时大多都是通过自定义ListView增加头部和底部来实现下拉刷新和上拉加载更多功能,使用RecyclerView也需要处理一些滑动事件才能达到分页的效果,下面是看了一些网上的开源代码后,自己实现的一个可下拉刷新和上拉加载的RecyclerView;
package com.lcp.arecyclerview.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v4.widget.SwipeRefreshLayout;
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.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import com.lcp.arecyclerview.R;
import java.util.Arrays;
/** * Created by Aislli on 2015/12/29. */
public class ARecycleView extends FrameLayout {
private int mPadding;
private int mPaddingTop;
private int mPaddingBottom;
private int mPaddingLeft;
private int mPaddingRight;
private boolean mClipToPadding;
private int mEmptyId;
private RecyclerView mRecyclerView;
private SwipeRefreshLayout mSwipeRefreshLayout;
private ViewStub mEmpty;
private View mEmptyView;
private ARecyclerBaseAdapter mAdapter;
private boolean isLoadingMore;
private OnLoadMoreListener onLoadMoreListener;
private RecyclerView.LayoutManager mLayoutManager;
/** * 数据总条数 */
private int mTotal;
private int mMoreProgressId;
private ViewStub mMoreProgress;
private View mMoreProgressView;
/** * 是否可以显示加载更多 */
private boolean canShowLoadmore;
int itemCount = 0;
public ARecycleView(Context context) {
super(context);
initViews();
}
public ARecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs);
initViews();
}
public ARecycleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
initViews();
}
protected void initAttrs(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.AislliRecyclerview);
try {
mPadding = (int) typedArray.getDimension(R.styleable.AislliRecyclerview_Padding, -1.1f);
mPaddingTop = (int) typedArray.getDimension(R.styleable.AislliRecyclerview_PaddingTop, 0.0f);
mPaddingBottom = (int) typedArray.getDimension(R.styleable.AislliRecyclerview_PaddingBottom, 0.0f);
mPaddingLeft = (int) typedArray.getDimension(R.styleable.AislliRecyclerview_PaddingLeft, 0.0f);
mPaddingRight = (int) typedArray.getDimension(R.styleable.AislliRecyclerview_PaddingRight, 0.0f);
mClipToPadding = typedArray.getBoolean(R.styleable.AislliRecyclerview_ClipToPadding, false);
mEmptyId = typedArray.getResourceId(R.styleable.AislliRecyclerview_EmptyView, 0);
mMoreProgressId = typedArray.getResourceId(R.styleable.AislliRecyclerview_layout_moreProgress, R.layout.view_more_progress);
} finally {
typedArray.recycle();
}
}
protected void initViews() {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.layout_recyclerview, this);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_list);
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swiperefresh_layout);
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
mSwipeRefreshLayout.setEnabled(false);
mSwipeRefreshLayout.setProgressViewOffset(false, 0, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources()
.getDisplayMetrics()));
if (mRecyclerView != null) {
mRecyclerView.setClipToPadding(mClipToPadding);
if (mPadding > 0) {
mRecyclerView.setPadding(mPadding, mPadding, mPadding, mPadding);
} else {
mRecyclerView.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
}
}
setScrollListener();
mMoreProgress = (ViewStub) view.findViewById(R.id.more_progress);
mMoreProgress.setLayoutResource(mMoreProgressId);
if (mMoreProgressId != 0)
mMoreProgressView = mMoreProgress.inflate();
mMoreProgress.setVisibility(View.GONE);
mEmpty = (ViewStub) view.findViewById(R.id.emptyview);
mEmpty.setLayoutResource(mEmptyId);
if (mEmptyId != 0)
mEmptyView = mEmpty.inflate();
mEmpty.setVisibility(View.GONE);
}
private void setScrollListener() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (null == onLoadMoreListener || isLoadingMore) {
return;
}
int currentCount = 0;
if (mLayoutManager instanceof LinearLayoutManager) {
currentCount = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition() + 1;
} else if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
Arrays.sort(lastVisibleItemPositions);
currentCount = lastVisibleItemPositions[lastVisibleItemPositions.length - 1] + 1;
}
itemCount = mAdapter.getItemCount();
if (canShowLoadmore && newState == RecyclerView.SCROLL_STATE_IDLE
&& currentCount == itemCount) {
if (mTotal > 0 && currentCount < mTotal) {
isLoadingMore = true;
mMoreProgressView.setVisibility(VISIBLE);
onLoadMoreListener.loadMore();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
public void setLayoutManager(RecyclerView.LayoutManager manager) {
mLayoutManager = manager;
mRecyclerView.setLayoutManager(mLayoutManager);
}
public void setAdapter(ARecyclerBaseAdapter adapter) {
mAdapter = adapter;
mRecyclerView.setAdapter(mAdapter);
if (mSwipeRefreshLayout != null)
mSwipeRefreshLayout.setRefreshing(false);
if (mAdapter != null)
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
updateHelperDisplays();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
updateHelperDisplays();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
updateHelperDisplays();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
updateHelperDisplays();
}
@Override
public void onChanged() {
super.onChanged();
updateHelperDisplays();
}
});
if ((adapter == null || mAdapter.getItemCount() == 0) && mEmptyId != 0) {
mEmpty.setVisibility(View.VISIBLE);
}
}
private void updateHelperDisplays() {
isLoadingMore = false;
if (mSwipeRefreshLayout != null)
mSwipeRefreshLayout.setRefreshing(false);
if (mAdapter == null)
return;
if (mAdapter.getItemCount() == 0) {
mEmpty.setVisibility(mEmptyId != 0 ? View.VISIBLE : View.GONE);
} else if (mEmptyId != 0) {
mEmpty.setVisibility(View.GONE);
}
}
/** * Sets the {@link RecyclerView.ItemAnimator} that will handle animations involving changes * to the items in this RecyclerView. By default, RecyclerView instantiates and * uses an instance of {@link android.support.v7.widget.DefaultItemAnimator}. Whether item animations are enabled for the RecyclerView depends on the ItemAnimator and whether * the LayoutManager {@link android.support.v7.widget.RecyclerView.LayoutManager#supportsPredictiveItemAnimations() * supports item animations}. * * @param animator The ItemAnimator being set. If null, no animations will occur * when changes occur to the items in this RecyclerView. */
public void setItemAnimator(RecyclerView.ItemAnimator animator) {
mRecyclerView.setItemAnimator(animator);
}
public void setHasFixedSize(boolean hasFixedSize) {
mRecyclerView.setHasFixedSize(hasFixedSize);
}
public void setRefreshing(boolean refreshing) {
if (mSwipeRefreshLayout != null)
mSwipeRefreshLayout.setRefreshing(refreshing);
mLayoutManager.scrollToPosition(0);
}
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) {
mSwipeRefreshLayout.setEnabled(true);
mSwipeRefreshLayout.setOnRefreshListener(listener);
}
/** * Set the load more listener of recyclerview * * @param onLoadMoreListener load listen */
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
public void setTotal(int mTotal) {
this.mTotal = mTotal;
canShowLoadmore = true;
}
public void showMoreProgress() {
mMoreProgress.setVisibility(View.VISIBLE);
}
public void hideMoreProgress() {
mMoreProgress.setVisibility(View.GONE);
canShowLoadmore = true;
mLayoutManager.scrollToPosition(itemCount);
}
public void hideLoading() {
if (mSwipeRefreshLayout != null)
mSwipeRefreshLayout.setRefreshing(false);
mMoreProgress.setVisibility(View.GONE);
canShowLoadmore = true;
}
private static boolean isParallaxHeader = false;
public static class CustomRelativeWrapper extends RelativeLayout {
private int mOffset;
public CustomRelativeWrapper(Context context) {
super(context);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (isParallaxHeader)
canvas.clipRect(new Rect(getLeft(), getTop(), getRight(), getBottom() + mOffset));
super.dispatchDraw(canvas);
}
public void setClipY(int offset) {
mOffset = offset;
invalidate();
}
}
/** * Add an {@link RecyclerView.ItemDecoration} to this RecyclerView. Item decorations can affect both measurement and drawing of individual item views. Item decorations are ordered. Decorations placed earlier in the list will be run/queried/drawn first for their effects on item views. Padding added to views will be nested; a padding added by an earlier decoration will mean further item decorations in the list will be asked to draw/pad within the previous decoration's given area. * * @param itemDecoration Decoration to add */
public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration) {
mRecyclerView.addItemDecoration(itemDecoration);
}
public interface OnLoadMoreListener {
void loadMore();
}
}
ARecycleView类的布局文件:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">
<ViewStub
android:id="@+id/more_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:inflatedId="@id/more_progress"/>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/more_progress">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@null"
android:scrollbars="none"/>
</android.support.v4.widget.SwipeRefreshLayout>
<ViewStub
android:id="@+id/emptyview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_marginTop="8dp"
android:visibility="gone"/>
</RelativeLayout>
调用代码:
package com.lcp.arecyclerview;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast;
import com.lcp.arecyclerview.adapter.RcListAdapter;
import com.lcp.arecyclerview.widget.ARecycleView;
import com.lcp.arecyclerview.widget.ARecyclerBaseAdapter;
import com.lcp.arecyclerview.widget.DividerItemDecoration;
import java.util.ArrayList;
/** * ListView and GridView * Created by Aislli on 2016/2/29. */
public class RecyclerListActivity extends Activity {
private ARecycleView mRecyclerView;
private ArrayList<String> list;
private RcListAdapter mReListAdapter;
private RecyclerView.LayoutManager linearLayoutManager;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_list);
initView();
initData();
}
private void initView() {
mRecyclerView = (ARecycleView) findViewById(R.id.recyclerview);
}
private void initData() {
int flag = getIntent().getIntExtra("flag", 0);
list = new ArrayList<>();
for (int i = 0; i < 60; i++) {
list.add("item:" + i);
}
if (flag == 0) {//线性布局,ListView效果
linearLayoutManager = new LinearLayoutManager(this);
mReListAdapter = new RcListAdapter(this, list);
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.HORIZONTAL));
} else if (flag == 1) {//GridView效果
linearLayoutManager = new GridLayoutManager(this, 3);
mReListAdapter = new RcListAdapter(this, list);
}
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setAdapter(mReListAdapter);
mRecyclerView.setTotal(list.size() + 6);//设置数据总数,加载完了就禁止掉加载更多事件
setListener();
}
int moreNum = 1;
int refreshNum = 1;
private void setListener() {
//下拉刷新
mRecyclerView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
mReListAdapter.insert(refreshNum++ + " Refresh things", 0);
mRecyclerView.setRefreshing(false);
}
}, 1500);
}
});
//上拉加载更多
mRecyclerView.setOnLoadMoreListener(new ARecycleView.OnLoadMoreListener() {
@Override
public void loadMore() {
handler.postDelayed(new Runnable() {
public void run() {
mReListAdapter.insert("More " + moreNum++, mReListAdapter.getItemCount());
mRecyclerView.hideMoreProgress();
}
}, 1500);
}
});
//点击事件
mReListAdapter.setOnItemClickListener(new RcListAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(getApplicationContext(), "position:" + position, Toast.LENGTH_SHORT).show();
}
});
//长按点击
mReListAdapter.setOnItemLongClickListener(new ARecyclerBaseAdapter.OnItemLongClickListener() {
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(getApplicationContext(), "long position:" + position, Toast.LENGTH_SHORT).show();
mReListAdapter.remove(position);
}
});
}
}
RecyclerView的下拉刷新是android.support.v4.widget.SwipeRefreshLayout控制实现的,滑动到底部自动加载下页内容主要在ARecyclerView类的这部分代码实现的:
private void setScrollListener() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (null == onLoadMoreListener || isLoadingMore) {
return;
}
int currentCount = 0;
//判断最后一个item的position
if (mLayoutManager instanceof LinearLayoutManager) {
currentCount = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition() + 1;
} else if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
Arrays.sort(lastVisibleItemPositions);
currentCount = lastVisibleItemPositions[lastVisibleItemPositions.length - 1] + 1;
}
itemCount = mAdapter.getItemCount();
if (canShowLoadmore && newState == RecyclerView.SCROLL_STATE_IDLE
&& currentCount == itemCount) {
if (mTotal > 0 && currentCount < mTotal) {
isLoadingMore = true;
mMoreProgressView.setVisibility(VISIBLE);
onLoadMoreListener.loadMore();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
解释下这段代码,判断条件流程:
1.加载更多的监听器是否为空,是否正在执行加载更多操作;
2.是否允许加载更多canShowLoadmore ,这个是在setTotal(int mTotal)方法里设置的,服务器返回的接口数据里,分页时一般都会返回total字段,调用ARecyclerView的setTotal方法设置进去后,加载完数据后会自动禁止掉加载更多的操作;
3.滑动状态是否为SCROLL_STATE_IDLE(停止滑动状态);
4.页面上显示出来的最后一个item的position是否等于当前适配器的总item数;
5.是否设置了数据总数量mTotal,页面上显示出来的最后一个item的position是否小于mTotal;
如果满足上面的所有条件,调用loadMore() 方法。
OK,上面这些就是实现RecyclerView的主要部分代码,建议有时间都自己自定义下RecyclerView这个类,可以增进自己对这个控件的了解,使用时更灵活,需要什么方法可以自己琢磨下往里加。
源码