近期我重新封装了RecyclerView 下拉刷新上拉加载更多,最新链接可以看这里http://blog.csdn.net/zly921112/article/details/53432959
这几天研究了下RecyclerView的使用和封装,发现还是蛮好用的,现在把学习成果分享给大家,主要是关于上拉加载更多的封装,之所以没有封装下拉刷新也是因为SwipeRefreshLayout的存在,并且它更加符合MD风格.
注意该封装只适用于类似listview这种列表形式的
附上效果

先来看看activity中使用该控件的代码
package com.byzk.www.recyclerviewpackage;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<String> dataList = new ArrayList<>();
private RefreshRecyclerView rv;
private MyAdapter myAdapter;
private SwipeRefreshLayout srl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initListener();
}
private void initView() {
srl = (SwipeRefreshLayout) findViewById(R.id.srl);
srl.setColorSchemeResources(android.R.color.holo_red_light, android.R.color.holo_blue_light, android.R.color.holo_green_light);
rv = (RefreshRecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setLoadMoreEnable(true);//允许加载更多
rv.setFooterResource(R.layout.item_footer);//设置脚布局
myAdapter = new MyAdapter(dataList);
rv.setAdapter(myAdapter);
}
private void initData() {
for (int i = 0; i < 20; i++) {
dataList.add("数据" + i);
}
rv.notifyData();
}
private void initListener() {
rv.setOnLoadMoreListener(new RefreshRecyclerView.OnLoadMoreListener() {
@Override
public void loadMoreListener() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
dataList.add("更多数据" + i);
}
rv.notifyData();//刷新数据
}
}, 2000);
}
});
srl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
srl.setRefreshing(false);
dataList.add(0, "最新数据");
rv.notifyData();//刷新数据
}
}, 2000);
}
});
}
private Handler handler = new Handler();
}
可以发现activity中,recyclerview的写法没有太大变化,只是有如下几点需要注意
1.如果想要加载更多需要调用(必须)
rv.setLoadMoreEnable(true)
2.如果希望加载更多时候有脚布局提示需要调用(不是必须的)
rv.setFooterResource(int res)
3.实现加载更多回调(必须)
rv.setOnLoadMoreListener()
4.刷新数据调用(必须)
rv.notifyData()
activity布局代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.byzk.www.recyclerviewpackage.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.byzk.www.recyclerviewpackage.RefreshRecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
下拉刷新用的是v4包中的SwipeRefreshLayout直接包裹在自己封装的RecyclerView的外层,上拉加载更多用的是自己封装的RecyclerView.
前方高能,接下来有一大波代码来袭,核心代码RecyclerView的封装如下
package com.byzk.www.recyclerviewpackage;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import jp.wasabeef.recyclerview.animators.adapters.SlideInBottomAnimationAdapter;
/**
* Author: zhuliyuan
* Time: 下午 3:26
* 将脚布局放在最后一个条目,当加载更多的时候显示,加载完成的时候隐藏
*
*/
public class RefreshRecyclerView extends RecyclerView {
private AutoLoadAdapter autoLoadAdapter;
public RefreshRecyclerView(Context context) {
this(context, null);
}
public RefreshRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private boolean isLoadingMore = false;//是否正在加载更多
private OnLoadMoreListener loadMoreListener;//加载数据监听
private boolean loadMoreEnable = false;//是否允许加载更多
private int footerResource = -1;//脚布局
private boolean footer_visible = false;//脚部是否可以见
private void init() {
setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (getAdapter() != null && getLayoutManager() != null) {
int lastVisiblePosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
int itemCount = getAdapter().getItemCount();
/**
* 控制下拉刷新回调
* itemCount != 0 排除没有数据情况
* lastVisiblePosition + 4 >= itemCount - 1 最后可见+4 >= 总条数 加载更多
* distanceY < 0 为上拉的时候才刷新
*/
if (distanceY < 0 && itemCount != 0 && lastVisiblePosition + 4 >= itemCount - 1 && !isLoadingMore && loadMoreEnable) {
Log.i("test","加载更多");
//正在加载更多
loading();
if (footerResource != -1){//有脚布局
//显示脚布局
footer_visible = true;
getAdapter().notifyItemChanged(itemCount - 1);
}
if (loadMoreListener != null) {
loadMoreListener.loadMoreListener();
}
}
}
}
});
}
/**
* 判断滑动方向
*/
private float distanceY = 0;
float startY = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float y = ev.getRawY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
distanceY = y - startY;
startY = y;
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public void setAdapter(Adapter adapter) {
SlideInBottomAnimationAdapter slideInBottomAnimationAdapter = new SlideInBottomAnimationAdapter(adapter);
slideInBottomAnimationAdapter.setDuration(600);
autoLoadAdapter = new AutoLoadAdapter(slideInBottomAnimationAdapter);//添加动画
super.setAdapter(autoLoadAdapter);
}
/**
* 设置是否允许加载更多
*
* @param isEnable
*/
public void setLoadMoreEnable(boolean isEnable) {
this.loadMoreEnable = isEnable;
}
/**
* 设置脚布局
*/
public void setFooterResource(int footerResource) {
this.footerResource = footerResource;
}
/**
* 加载完成
*/
private void loadMoreComplete() {
this.isLoadingMore = false;
}
/**
* 正在刷新
*/
private void loading(){
this.isLoadingMore = true;//设置正在刷新
}
/**
* 加载更多数据回调
*
* @param listener
*/
public void setOnLoadMoreListener(OnLoadMoreListener listener) {
this.loadMoreListener = listener;
}
public interface OnLoadMoreListener {
void loadMoreListener();//上拉刷新
}
/**
* 刷新数据
*/
public void notifyData() {
if (getAdapter() != null) {
loadMoreComplete();
if(footerResource != -1 && loadMoreEnable){
//隐藏脚布局
footer_visible = false;
}
getAdapter().notifyDataSetChanged();
}
}
public class AutoLoadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Adapter dataAdapter;//数据adapter
private final int TYPE_FOOTER = Integer.MAX_VALUE;//底部布局
public AutoLoadAdapter(RecyclerView.Adapter adapter) {
this.dataAdapter = adapter;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1 && loadMoreEnable && footerResource != -1 && footer_visible) {
return TYPE_FOOTER;
}
if (dataAdapter.getItemViewType(position) == TYPE_FOOTER) {
throw new RuntimeException("adapter中itemType不能为:" + Integer.MAX_VALUE);
}
return dataAdapter.getItemViewType(position);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder holder = null;
if (viewType == TYPE_FOOTER) {//脚部
holder = new FooterViewHolder(LayoutInflater.from(getContext()).inflate(footerResource, parent, false));
} else {//数据
holder = dataAdapter.onCreateViewHolder(parent, viewType);
}
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int itemViewType = getItemViewType(position);
if (itemViewType != TYPE_FOOTER) {
dataAdapter.onBindViewHolder(holder, position);
}
}
@Override
public int getItemCount() {
if (dataAdapter.getItemCount() != 0) {
int count = dataAdapter.getItemCount();
if (loadMoreEnable && footerResource != -1 && footer_visible) {
count++;
}
return count;
}
return 0;
}
public class FooterViewHolder extends RecyclerView.ViewHolder {
public FooterViewHolder(View itemView) {
super(itemView);
}
}
}
}
这里封装了上拉加载更多,自己写了个adpter壳子,当允许上拉加载更多并且设置了脚布局的时候将脚布局加载到最后一个条目,根据最后可见条目位置与总条数的比较来回调加载更多刷新,其他时候数据都来源于自己写的DataAdapter,下拉刷新可以按同样原理实现为了MD设计风格,这里我就直接套的SwipeRefreshLayout了.
提供数据Adapter如下
package com.byzk.www.recyclerviewpackage;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
* Author: zhuliyuan
* Time: 下午 5:36
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<String> dataList;
public MyAdapter(List<String> dataList) {
this.dataList = dataList;
}
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder myViewHolder = new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false));
return myViewHolder;
}
@Override
public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
holder.tv.setText(dataList.get(position));
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView tv;
public MyViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
@Override
public int getItemCount() {
return (dataList == null || dataList.size() == 0)?0:dataList.size();
}
}
item布局如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
card_view:cardBackgroundColor="#0094ff"
card_view:cardCornerRadius="6dp"
card_view:cardElevation="5dp">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="哈哈哈"
android:textColor="#fff" />
</android.support.v7.widget.CardView>
提供数据adapter和item布局都是普通写法,这里不过多赘言
源码传送门
项目中recyclerview动画地址https://github.com/wasabeef/recyclerview-animators
到此封装完成,,如果有bug或者建议欢迎留言.