更新说明:之前看评论里的童鞋们给我反映问题,很感谢大家指出,针对出现的问题我进行了一些改进。同时将加载更多的这个功能从主体的adapter中分离出来,使用了装饰者模式,降低了代码的耦合,这样便于维护和修改。
装饰者模式是常用的Java设计模式之一,不熟悉的童鞋可以自行查阅资料,先了解了装饰者模式看以下内容会容易懂一点~这个设计模式在Android里也是广泛应用,最典型的就是Context的应用了。
……………………….我是分割线…………………………………………
Recyclerview是 Listview 的升级版本,在项目中使用较为广泛,官方也推荐我们使用 Recyclerview 来代替 Listview,在此就不多说 Recyclerview 的优势特点 balala了。。。
在实际项目中,列表通常是分页的,请求服务器也只会一次请求若干条,按需加载,这样比较节省流量,这样就有了我们很常见的上拉加载更多的功能,具体的实现效果如下图:
该activity的布局文件:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="io.geek.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
LinearLayout>
底部加载更多的view布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="正在加载..."
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
LinearLayout>
底部提示到底的view布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:padding="8dp"
android:gravity="center"
android:layout_height="wrap_content">
<TextView
android:text="已经到底了"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
LinearLayout>
数据item的view:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:background="@color/colorAccent"
android:layout_width="50dp"
android:layout_height="50dp"/>
<TextView
android:gravity="center"
android:id="@+id/tv_content"
tools:text="我是第一项"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
LinearLayout>
接下来就是代码的编写了,开头就已经讲到过我们在这要用到装饰者模式,其实装饰者模式并没有多么复杂,可以理解成给一个类装饰点新功能,并且不会影响到这个类本来的功能。装饰者模式写法也很简单,分为以下几步套路:
创建基类(装饰类和被装饰类共同父类),首先写一个抽象类BaseAdapter,继承自RecyclerView.Adapter,这个Adapter中只封装了与数据源相关的字段和方法:
/**
* 与数据源相关的字段和方法封装在父类中
*/
public abstract class BaseAdapter<T> extends RecyclerView.Adapter {
protected List dataSet = new ArrayList<>();
public void updateData(List dataSet) {
this.dataSet.clear();
appendData(dataSet);
}
public void appendData(List dataSet) {
if (dataSet != null && !dataSet.isEmpty()) {
this.dataSet.addAll(dataSet);
notifyDataSetChanged();
}
}
public List getDataSet() {
return dataSet;
}
@Override
public int getItemCount() {
return dataSet.size();
}
}
创建装饰类,然后开始编写加载更多的adapter的包装类,这个类继承自BaseAdpater,这个装饰类中只负责处理加载更多的逻辑,代码如下:
/**
* 在这个装饰器中,只做与加载更多相关操作。
public class LoadMoreAdapterWrapper extends BaseAdapter {
private BaseAdapter mAdapter;
private static final int mPageSize = 10;
private int mPagePosition = 0;
private boolean hasMoreData = true;
private OnLoad mOnLoad;
public LoadMoreAdapterWrapper(BaseAdapter adapter, OnLoad onLoad) {
mAdapter = adapter;
mOnLoad = onLoad;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == R.layout.list_item_no_more) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new NoMoreItemVH(view);
} else if (viewType == R.layout.list_item_loading) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new LoadingItemVH(view);
} else {
return mAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof LoadingItemVH) {
requestData(mPagePosition, mPageSize);
} else if (holder instanceof NoMoreItemVH) {
} else {
mAdapter.onBindViewHolder(holder, position);
}
}
private void requestData(int pagePosition, int pageSize) {
//网络请求,如果是异步请求,则在成功之后的回调中添加数据,并且调用notifyDataSetChanged方法,hasMoreData为true
//如果没有数据了,则hasMoreData为false,然后通知变化,更新recylerview
if (mOnLoad != null) {
mOnLoad.load(pagePosition, pageSize, new ILoadCallback() {
@Override
public void onSuccess() {
notifyDataSetChanged();
mPagePosition = (mPagePosition + 1) * mPageSize;
hasMoreData = true;
}
@Override
public void onFailure() {
hasMoreData = false;
}
});
}
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
if (hasMoreData) {
return R.layout.list_item_loading;
} else {
return R.layout.list_item_no_more;
}
} else {
return mAdapter.getItemViewType(position);
}
}
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 1;
}
static class LoadingItemVH extends RecyclerView.ViewHolder {
public LoadingItemVH(View itemView) {
super(itemView);
}
}
static class NoMoreItemVH extends RecyclerView.ViewHolder {
public NoMoreItemVH(View itemView) {
super(itemView);
}
}
}
public interface OnLoad {
void load(int pagePosition, int pageSize, ILoadCallback callback);
}
public interface ILoadCallback {
void onSuccess();
void onFailure();
}
解析如下:
1. 该类中的传入的mAdapter就是需要被装饰的adapter,mOnLoad是自定义的一个接口,传入之后可以回调处理加载更多数据。
2. 首先重写getItemCount方法,因为在列表中最后始终会有一个加载更多或者是到底提示的view,所以item的数目始终是数据源的数目多一个。
3. 重写getItemType方法:首先判断position是否是最后一个,如果不是的话,则直接返回被装饰的mAdapter的getItemType;如果是的话判断hasMoreData变量,是true则返回加载更多的布局文件ID,反之返回到底的布局文件ID。这里的hasMoreData是我们自己定义的一个boolean字段,通过改变这个值就可以最后一个view应该是加载更多还是到底。
4. 重写onCreateViewHolder方法,在这个方法中通过判断viewType的类型返回不同的ViewHolder。
5. 重写onBindViewHolder方法,在这个方法中,判断holder的类型的不同做出不同的操作,如果holder是加载更多的holder,那么表示加载更多的view正在展示,这时候就应该做出加载更多的操作,这个操作放在了requestData方法中。
6. 在requestData方法中,触发了OnLoad接口中的load回调,传入的参数有当前的页码加载数据一页的大小,加载完成之后的回调接口,可以看到加载完成之后会进行不同的作,如果成功,则设置hasMoreData为true,并且通知数据发生改变,更新列表,改变前页码;如果失败的话,则把hasMoreData设置为false。
创建被装饰类,同样这个类也需要继承自BaseAdapter:
/**
* 被装饰类要和装饰类继承自同一父类
*/
public class MyAdapter extends BaseAdapter<String> {
private Context mContext;
public MyAdapter(Context context) {
mContext = context;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_mine, parent, false);
return new MyViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder) holder).bind(getDataSet().get(position));
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
public MyViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_content);
}
public void bind(CharSequence content) {
mTextView.setText(content);
}
}
}
最后一步就是如何使用,这个adapter就是一个普通的没有上拉加载功能的adapter,如果要给它加上这个功能,只需要这么使用:
public class MainActivity extends AppCompatActivity {
RecyclerView mRecyclerView;
BaseAdapter mAdapter;
int loadCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
//创建被装饰者类实例
final MyAdapter adapter = new MyAdapter(this);
//创建装饰者实例,并传入被装饰者和回调接口
mAdapter = new LoadMoreAdapterWrapper(adapter, new OnLoad() {
@Override
public void load(int pagePosition, int pageSize, final ILoadCallback callback) {
//此处模拟做网络操作,2s延迟,将拉取的数据更新到adpter中
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List dataSet = new ArrayList();
for (int i = 0; i < 10; i++) {
dataSet.add("我是一条数据");
}
//数据的处理最终还是交给被装饰的adapter来处理
adapter.appendData(dataSet);
callback.onSuccess();
//模拟加载到没有更多数据的情况,触发onFailure
if (loadCount++ == 3) {
callback.onFailure();
}
}
}, 2000);
}
});
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
}
}
这样就完整实现了 Recylerview 的加载更多功能,使用装饰者模式使得整个代码结构更加清晰,在被装饰的adapter中就不用处理太多逻辑,专心处理数据展示即可,易于维护。
其中比较绕的地方可能就是数据加载的回调接口,不过仔细理一下还是没有多大问题,总体来说还是很简单的,如果还是有不明白的,可能你对 Recylerview 的使用方法还不够了解,可以自行学习。
有不当之处请大家批评指正,谢谢:)