一、综述
关于RecyclerView的上拉加载和下拉刷新,有很多的开源框架来帮助我们实现,并且有些框架封装的很好,但是很多时候我们并不需要那么强大的功能,同时,为了上拉加载而引入一堆三方框架,实则一方面使得代码变得冗余,另一方面或许也埋下了bug的伏笔,一旦bug出现,便不是好处理的问题,所以,有了写下这遍文章的想法,以最简洁的方式来实现recyclerview的上拉加载功能。
二、交互逻辑
通常在访问网络的时候,服务端会有大量的数据,一次拉取下来显然是不合理的做法,所以便有了分段拉取的需求。服务端在网络接口里暴露给客户端开始的序号和结束的序号,客户端通过传入的序号来获取相应的数据。
三、代码实现
首先来看Adapter,由于需要在底部加入一个状态显示的footer,便要在Adapter里设置两种不同类型的item
private static final int TYPE_NORMAL = 0;
private static final int TYPE_FOOTER = 1;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_NORMAL) {
return new YourAdapter.NormalHolder(LayoutInflater.from(context).inflate(R.layout.item_normal, null));
} else {
return new YourAdapter.FootHolder(LayoutInflater.from(context).inflate(R.layout.item_footer, null));
}
}
注意此时的getItemCount()和getItemViewType()方法和不添加footer时的不同。
因为添加了footer,所以列表数量变为list.size() + 1
@Override
public int getItemCount() {
return list.size() + 1;
}
返回不同的itemType
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return TYPE_FOOTER;
} else {
return TYPE_NORMAL;
}
}
同时,需要添加一些辅助方法
获取最后一个数据条目的真实位置
public int getRealLastPosition() {
return list.size();
}
是否隐藏footer
public boolean isFadeTips() {
return fadeTips;
}
刷新列表
public void updateList(boolean hasMore) {
this.hasMore = hasMore;
notifyDataSetChanged();
}
然后,来看Activity里的逻辑
大部分设置ReyclerView的逻辑与平常一致,只需要重点注意这些地方,通过引入lastVisibleItem和PAGE_Count两个量来控制请求的节点和请求的数量,以及addOnScrollLister的使用,在这个接口里,会监听上拉的事件。
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!mAdapter.isFadeTips() && lastVisibleItem + 1 == mAdapter.getItemCount()) {
updateRecyclerView(mAdapter.getRealLastPosition(), mAdapter.getRealLastPosition() + PAGE_COUNT);
}
if (mAdapter.isFadeTips() && lastVisibleItem + 2 == mAdapter.getItemCount()) {
updateRecyclerView(mAdapter.getRealLastPosition(), mAdapter.getRealLastPosition() + PAGE_COUNT);
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
}
});
下面是Activity和Adapter的完整代码
public class YourActivity extends BaseActivity {
private Context mContext;
private RecyclerView mRecyclerView;
private SwipeRefreshLayout mSwipeRefreshLayout;
private List<YourDataBean> mList;
private int lastVisibleItem = 0;
private final int PAGE_COUNT = 10;
private YourAdapter mAdapter;
private LinearLayoutManager mLayoutManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
initModule();
initView();
initController();
}
private void initModule() {
mContext = YourActivity.this;
mList = new ArrayList<>();
requestData(0, PAGE_COUNT);
}
private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);
mSwipeRefreshLayout.setColorSchemeColors(ContextCompat.getColor(mContext, R.color.swiprefresh_scheme1),
ContextCompat.getColor(mContext, R.color.swiprefresh_scheme2), ContextCompat.getColor(mContext, R.color.swiprefresh_scheme3),
ContextCompat.getColor(mContext, R.color.swiprefresh_scheme4));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshData();
}
});
}
private void initController() {
mAdapter = new YourAdapter(this, mList);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Drawable drawable = ContextCompat.getDrawable(mContext, R.drawable.divider);
mRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(mContext, drawable, 2));
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!mAdapter.isFadeTips() && lastVisibleItem + 1 == mAdapter.getItemCount()) {
updateRecyclerView(mAdapter.getRealLastPosition(), mAdapter.getRealLastPosition() + PAGE_COUNT);
}
if (mAdapter.isFadeTips() && lastVisibleItem + 2 == mAdapter.getItemCount()) {
updateRecyclerView(mAdapter.getRealLastPosition(), mAdapter.getRealLastPosition() + PAGE_COUNT);
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
}
});
}
private void updateRecyclerView(int fromIndex, int toIndex) {
requestData(fromIndex, toIndex);
}
private void refreshData() {
mSwipeRefreshLayout.setRefreshing(true);
mAdapter.resetDatas();
requestData(0, PAGE_COUNT);
}
public void requestData(int start, int count) {
YourNetWorkRequest.get(requestData(start, count), new YourStatusListener(){
@Override
public void successCallBack() {
//这里使用自己的网络请求,填充好数据之后,刷新列表即可。
setData(mList);
if (mList.size() > oldSize) {
mAdapter.updateList(true);
} else {
mAdapter.updateList(false);
}
mSwipeRefreshLayout.setRefreshing(false);
}
});
}
}
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private List<YourDataBean> list;
private static final int TYPE_NORMAL = 0;
private static final int TYPE_FOOTER = 1;
private boolean hasMore = true;
private boolean fadeTips = false;
public YourAdapter(Context context, List<YourDataBean> list) {
this.context = context;
this.list = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_NORMAL) {
return new ExpensesRecordAdapter.NormalHolder(LayoutInflater.from(context).inflate(R.layout.item_normal, null));
} else {
return new ExpensesRecordAdapter.FootHolder(LayoutInflater.from(context).inflate(R.layout.item_footer, null));
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ExpensesRecordAdapter.NormalHolder) {
//使用你自己的数据显示逻辑
} else {
((YourAdapter.FootHolder) holder).tips.setVisibility(View.VISIBLE);
if (hasMore) {
fadeTips = false;
if (list.size() > 0) {
((YourAdapter.FootHolder) holder).tips.setText("正在加载更多...");
}
} else {
if (list.size() > 0) {
((YourAdapter.FootHolder) holder).tips.setText("没有更多数据了");
((YourAdapter.FootHolder) holder).tips.setVisibility(View.GONE);
fadeTips = true;
hasMore = true;
}
}
}
}
@Override
public int getItemCount() {
return list.size() + 1;
}
public int getRealLastPosition() {
return list.size();
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return TYPE_FOOTER;
} else {
return TYPE_NORMAL;
}
}
public boolean isFadeTips() {
return fadeTips;
}
public void resetDatas() {
list.clear();
}
public void updateList(boolean hasMore) {
this.hasMore = hasMore;
notifyDataSetChanged();
}
class NormalHolder extends RecyclerView.ViewHolder {
public NormalHolder(View itemView) {
super(itemView);
}
}
class FootHolder extends RecyclerView.ViewHolder {
private TextView tips;
public FootHolder(View itemView) {
super(itemView);
tips = (TextView) itemView.findViewById(R.id.tips);
}
}
}
SwipeRefreshLayout的用法或许大家都很熟悉了,这里就不展开了,新接触的朋友可以去其他地方看看,SwipeRefreshLayout的下拉刷新相比于上拉加载实现起来更容易。
footer的布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="vertical">
<TextView
android:id="@+id/tips"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_centerInParent="true"
android:gravity="center"
android:textColor="#333333"
android:textSize="15sp" />
</RelativeLayout>
分割线的divider也贴出来了,各位看官老爷还满意吗
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="0.5dp" />
<solid android:color="#e5e5e5" />
</shape>
整体框架就是这样,使用的时候换成自己的数据源和item显示就可以了,footer的动画效果、headerview等其他扩展的东西,各位可以根据自己的具体需求去定制,相信对于看到这里的你,都已经不是什么有难度的事了。