前言
最近在使用RecyclerView,之所以不使用ListView是因为RecyclerView可以十分方便的实现横向的列表显示,这就是我抛弃ListView的原因。但是当使用纵向的RecyclerView的时候,问题就出现了。因为ListView在很多情况下是要上拉加载数据的,突然换到了RecyclerView就不知道该怎么办了。当然网上也有一些实现了屏蔽所包含View类型的上拉加载控件,但是我还是想要自己去继承RecyclerView来实现这个功能,那么就去干吧!
实现
因为之前写过ListView的加载,那时候实现的时候是给ListView加一个FooterView,然后控制FooterView显示的时机就可以了,那么RecyclerView可不可以呢?答案是“不可以”!因为RecyclerView没有添加FooterView的方法,根本不能添加何谈控制时机啥的。那么就换个思路吧,有了!既然不能添加FooterView,那么我就把最后一个Item项变成FooterView,这样实现的效果和添加一个FooterView是一样的。思路是这样的,那么该怎么去做呢?
很明显是要在Adapter上下功夫,在方法onCreateViewHolder中,我们返回的不是正常的ViewHolder而是我们定义的一个表示最后一个Item的ViewHolder,就叫FooterHolder吧。但是我们该怎么判断什么时候返回FooterHolder,什么时候返回正常Holder呢?
这个时候就要用到getItemViewType(int position)方法,这个方法传入的是Item的position,我们可以做如下的判断:
if (position + 1 == getItemCount()){
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
意思是说如果position+1等于总共的Item数量,那么当前Item表示的就是最后一个Item了,也就是我们要显示的加载界面。
然后再回到onCreateViewHolder(ViewGroup parent, int viewType)方法,这里传入了viewType方法,也就是getItemViewType得到的值,我们可以利用这个值来进行判断了,如下所示:
if(viewType==TYPE_ITEM){
//返回正常的Holder
}else if(viewType==TYPE_FOOTER){
//返回FooterHolder
}
好了,能够将FooterHolder加入RecyclerView的最后一项了,那么接下来的就是怎样正确的将它显示出来。
由ListView我们知道,当滑动到最后一项的时候,我们就将它显示,所以我们要对滑动进行监听,检测什么时候滑到最后一项。我们就新建一个类PTRecyclerView让它继承RecyclerView,然后在类的内部调用setOnScrollListener方法,这样就可以对滑动事件进行监听。传入的OnScrollListener我们需要覆盖其中的两个方法,如下所示:
this.setOnScrollListener(new OnScrollListener(){
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState){
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager manager = (LinearLayoutManager) getLayoutManager();
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisibleItem + 1 == manager.getItemCount()){
//调用加载更多的方法
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy){
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = ((LinearLayoutManager)getLayoutManager()).findLastVisibleItemPosition();
}
});
在onScrolled方法中,我用lastVisibleItem 来记录当前可见得最后一项的位置,这是为onScrollStateChanged方法服务的。在onScrollStateChanged方法中,利用lastVisibleItem 我们判断是不是可见的最后一项是RecyclerView的最后一项,如果是的话,那么就应该显示正在加载的界面,然后调用加载更多的方法。
当然在RecyclerView内部我们我们不能直接调用加载更多的方法,而是应该用一个接口,然后在Activity中实现此接口,通过接口来实现加载更多,把加载交给Activity层来进行操作。
这样能够实现加载更多,但是这样并不是就结束了。进行加载的话我们还需要对加载的结果进行处理,如果加载失败怎么办,如果没有更多数据了该怎么办,所以还需要后续的处理。
我想要显示的是下面的三种情况:
那么分别在什么时候显示呢?因为这涉及到最后一项的界面的改变,我们要拿到这个界面的对象,在PTRecyclerView内部我们是拿不到这个对象的,或者说就算拿到了也不行,我们不能把操作放在PTRecyclerView中,它只是告诉我们什么时候该加载,至于怎么加载,加载结果如何都不是它负责的事情了。所以我们要在Adapter内部来实现。
这里我使用了别人写好的一个通用Adapter,一个RecyclerView就要对应一个Adapter,又很多的方法和逻辑又是一样的,所以就把Adapter进行封装,屏蔽了那些一样的内容,只把不一样的内容让你去实现。所以我就在这个基础上加入加载的功能,目的也是实现封装,拿过来就能使用,去除很多重复的代码。
我们来看一下FooterHolder的部分实现:
public FooterHolder(View itemView){
super(itemView);
mFooterTextView = (TextView) itemView.findViewById(R.id.tv_footer);
mProgressBar = (ProgressBar) itemView.findViewById(R.id.footer_progressbar);
mFooterTextView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
if(mAdapter!=null&&mAdapter.getLoadState()==BaseRecyclerViewAdapter.STATE_FAILURE){
mFooterTextView.setText("加载中···");
mProgressBar.setVisibility(View.VISIBLE);
mAdapter.reload();
mAdapter.setLoadState(BaseRecyclerViewAdapter.STATE_LOADING);
}
}
});
}
从图中我们可以看到加载界面有一个TextView和一个ProgressBar,在构造方法中,我对TextView设置了点击监听,判断如果当前的加载状态是加载失败的话,那么点击之后就要重新进行加载。
在Activity中,我们实现那个加载的接口中的方法loadMore:
@Override
public void loadMore(){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
int result = new Random().nextInt(3);
if(result==1){
//加载成功,处理数据
for(int i='A';i<='Z';i++)
mDatas.add((char)i+"");
mAdapter.notifyDataSetChanged();
mAdapter.finishLoad(BaseRecyclerViewAdapter.STATE_SUCCESS);
}else if(result==0){
//加载失败
mAdapter.finishLoad(BaseRecyclerViewAdapter.STATE_FAILURE);
}else if(result==2){
//没有更多数据了
mAdapter.finishLoad(BaseRecyclerViewAdapter.STATE_FINISH);
}
}
},3000);
}
这里我就简单模拟了一下加载的结果,加载成功就把数据加到列表中,然后调用notifyDataSetChanged方法,还有加载失败和没有更多数据的处理。
当然PTRecyclerView这个类可以选择要不要实现上拉加载的功能,这样一个类就可以实现两个功能了。我提供了一个方法,由Adapter实现。
private boolean mCanLoadMore = false; //说明是否实现上拉加载功能
public void setCanLoadMore(boolean loadMore){
mCanLoadMore = loadMore;
}
最后我们来看一下效果: