关于RecyclerView底部刷新实现的文章已经非常多了,但大都仅仅介绍了其基本原理和框架,对当中的非常多细节没有交代,无法直接使用。
本文会着重介绍RecyclerView底部刷新实现的一些细节处理。
1. 顶部刷新和底部刷新
顶部刷新和底部刷新都是列表中两种常见的交互方式。顶部刷新通常相应更新数据,更新的数据会替换掉当前数据。
而底部刷新则相应获取很多其它的数据,很多其它的数据会加入在当前数据的后面。
顶部刷新和底部刷新在其它文章中很多其它的被称为下拉刷新和上拉载入很多其它,只是个人并不喜欢这样的称谓。每次提及上拉和下拉时都会感觉非常困惑。须要思考一下上拉和下拉究竟相应哪个操作。所以这里将这两种操作称为顶部刷新和底部刷新。当然假设读者没有这个困扰。认为非常easy区分上拉和下拉,不烦还是延续这样的称谓。
本文仅仅会介绍底部刷新。对顶部刷新会在后面的文章中再介绍。
2. RecyclerView底部刷新的原理
RecyclerView底部刷新的原理非常简单,就是在RecyclerView最底部加入一个表示载入中的View,然后监听RecyclerView的滑动事件,当RecyclerView滑动时。推断是否滑动到了RecyclerView的底部,也就是最后一个载入中的View是否可见,假设滑动到了RecyclerView底部,则运行底部刷新操作。获取很多其它数据。最后当获取很多其它数据完毕后,更新RecyclerView的Adapter。
3. RecyclerView底部刷新的一般实现
依据上述RecyclerView底部刷新的实现原理,能够知道RecyclerView底部刷新实际上包括例如以下步骤。注意这里的步骤并不代表代码的书写顺序,它很多其它的表示的是代码运行的顺序。
为RecyclerView底部加入一个表示载入中的View
设置RecyclerView的滑动事件监听,在滑动过程中。依据底部View是否可见,决定是否运行底部刷新操作
运行底部刷新时,获取很多其它数据
获取完数据后。通知Adapter更新RecyclerView
现分别介绍这4个步骤的实现。
在这之前,先限定一个约束条件。我们知道在使用RecyclerView时都须要调用其setLayoutManager()方法设置其LayoutManager。在V7包实现了三种类型的LayoutManager。即LinearLayoutManager。GridLayoutManager和StaggeredGridLayoutManager。这三种类型的LayoutManager在实现底部刷新时会有一些细节上的差异。为了简化描写叙述和方便理解。在这里介绍RecyclerView底部刷新的一般实现时,仅仅考虑LinearLayoutManager,对其它两种类型有差异的地方会在后文单独说明。
3.1 为RecyclerView底部加入一个表示载入中的View
表示载入中的View
这个表示载入中的View一般会使用一个居中显示的ProgressBar来表示。其布局例如以下。
android:layout_width="match_parent"
android:layout_height="40dp">
android:layout_gravity="center"
android:layout_width="30dp"
android:layout_height="30dp"/>
只是这并不是是强制要求。其详细样式能够依据须要自由定义。
后文会将这个表示载入中的View称为底部刷新View(Bottom Refresh View)。
为RecyclerView加入底部刷新View
为RecyclerView加入底部刷新View通常是通过将底部刷新View作为RecyclerView的Item来实现的。
为此须要改写RecyclerView Adapter的下面几个方法。
getItemCount
RecyclerView Adapter的getItemCount方法返回的是item的数量,既然要将底部刷新View作为RecyclerView的Item加入到RecyclerView中,就须要在原有item数量基础上加1。
比如:
@Override
public int getItemCount() {
return mList.size() + 1;
}
getItemViewType
RecyclerView Adapter的getItemViewType方法返回的是item的类型。为了将底部刷新View相应的item和其它item区分开。须要将底部刷新View作为一个单独的类型返回。
比如:
@Override
public int getItemViewType(int position) {
if (position < mList.size()) {
return TYPE_NORMAL_ITEM;
} else {
return TYPE_BOTTOM_REFRESH_ITEM;
}
}
onCreateViewHolder
RecyclerView Adapter的onCreateViewHolder方法用来创建ViewHolder。这里首先须要为底部刷新View定义一个ViewHolder。然后依据item的类型来决定要创建哪个ViewHolder。
比如:
// 定义底部刷新View相应的ViewHolder
private class BottomRefreshViewHolder extends RecyclerView.ViewHolder {
BottomViewHolder(View itemView) {
super(itemView);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 假设是底部刷新View,则载入底部刷新View布局,并创建底部刷新View相应的ViewHolder
if (viewType == TYPE_BOTTOM_REFRESH_ITEM) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_bottom_refresh_item, parent, false);
return new BottomRefreshViewHolder(view);
}
// 假设是其它类型的View,则依照正常流程创建普通的ViewHolder
else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_normal_item, parent, false);
return new NormalViewHolder(view);
}
}
onBindViewHolder
RecyclerView Adapter的onBindViewHolder方法用来将ViewHolder和相应的数据绑定起来。因为底部刷新View并不须要绑定不论什么数据,所以这里不须要对底部刷新ViewHolder做特别的处理,仅仅须要推断下是否是底部刷新ViewHolder就能够了。
比如:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (!(holder instanceof BottomRefreshViewHolder)) {
...
}
}
完毕了上述步骤之后即为RecyclerView底部加入一个底部刷新View
3.2 滑动事件处理
设置滑动事件监听
RecyclerView提供了addOnScrollListener()方法来设置滑动事件监听,仅仅须要将监听滑动事件的RecyclerView.OnScrollListener对象作为參数传递进去就可以。
比如:
addOnScrollListener(onScrollListener);
onScrollStateChanged()和onScrolled()
RecyclerView.OnScrollListener是一个抽象类,它包括两个方法onScrollStateChanged()和onScrolled()。
onScrollStateChanged()方法会在每次滑动状态发生改变时调用。
比如,由精巧状态变为滑动状态,或者由滑动状态变为精巧状态时,onScrollStateChanged()方法都会被调用。
onScrolled()方法会在RecyclerView滑动时被调用。即使手指离开了屏幕,仅仅要RecyclerView仍然在滑动onScrolled()就会被不断调用。
理论上来说。我们既能够将推断底部View是否可见和运行底部刷新操作的过程放到onScrollStateChanged()方法中运行,也能够将其放到onScrolled()方法中运行。
但放到不同方法中运行在用户体验上会产生一些不同。
假设将推断底部View是否可见和运行底部刷新操作的过程放到onScrollStateChanged()方法中运行,意味着是以一次滑动过程的终于状态来决定是否要运行底部刷新。假设在一次滑动过程中间,底部View已经可见,可是终于停下来的时候底部View是不可见的,那么将不会运行底部刷新操作。
假设将推断底部View是否可见和运行底部刷新操作的过程放到onScrolled()方法中运行,意味着仅仅要在一次滑动过程中间底部View可见。那么将会立马触发底部刷新操作。
观察大部分的APP。都是仅仅要出现底部载入中View。就会開始运行底部刷新操作,这也和一般用户的认知相一致。所以,一般我们都会将推断底部View是否可见和运行底部刷新操作的过程放到onScrolled()方法中运行。可是onScrollStateChanged()方法仍然是实用的。有些辅助的逻辑会放到当中来运行。详细哪些逻辑须要放到onScrollStateChanged()方法中会在文章后面提到。
推断底部刷新View是否可见
推断底部刷新View是否可见是实现RecyclerView底部刷新功能的关键。
只是幸好它的实现并不复杂。
在LinearLayoutManager中提供了一个方法能够获取到当前最后一个可见的item在RecyclerView Adapter中的位置,假设这个位置恰好等于RecyclerView Adapter中item的数量减1。那么就表示底部刷新View已经可见了。
这也非常easy理解,比如RecyclerView Adapter中有55个item,因为Adapter中的位置都是从0開始的,所以这55个item的位置就是从0到54。最后一个item(也就是底部刷新View相应的item)的位置是54。假设当前最后一个可见的item位置为54,那么就表示底部刷新View是可见的。
对LinearLayoutManager,能够调用其findLastVisibleItemPosition()方法来获取当前最后一个可见的item在RecyclerView Adapter中的位置。
演示样例代码例如以下。
private int getLastVisibleItemPosition() {
RecyclerView.LayoutManager manager = getLayoutManager();
if (manager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) manager).findLastVisibleItemPosition();
}
return NO_POSITION;
}
private boolean isBottomViewVisible() {
int lastVisibleItem = getLastVisibleItemPosition();
return lastVisibleItem != NO_POSITION && lastVisibleItem == getAdapter().getItemCount() - 1;
}
运行底部刷新操作
将上述几个步骤组合在一起就能够得到完整的滑动事件处理过程。演示样例代码例如以下。
RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (isBottomViewVisible()) {
requestMoreData();
}
}
};
addOnScrollListener(onScrollListener);
3.3 获取很多其它数据
获取数据的流程一般都是通过调用约定的接口从服务端获取数据,这属于业务逻辑。这里不做介绍了。
3.4 更新RecyclerView
获取数据一般都是异步过程,在获取数据完毕后,调用RecyclerView Adapter的相关方法更新RecyclerView。
因为是获取很多其它数据,所以一般能够调用notifyItemInserted()或者notifyItemRangeInserted()来更新RecyclerView。
至此。RecyclerView底部刷新的基本实现就已经完毕了。
4. 底部刷新功能的封装
上述底部刷新功能的实现,包括了两部分的改动,一部分是对RecyclerView自身的一些设置,比如设置滑动事件监听,推断底部刷新View是否可见等。
另外一部分是对RecyclerView Adapter的改动,也就是为RecyclerView加入底部刷新View。因为一个app中通常都会有多个界面须要实现底部刷新功能,假设每一个要实现底部刷新功能的界面都这样实现一遍,实在是太麻烦,也会使原本的代码变得复杂和臃肿。因此,须要将上述底部刷新功能的实现封装在一起。
对第一部分RecyclerView自身的一些设置,能够非常easy的通过继承RecyclerView来实现封装,然后在代码和xml中使用这个继承之后的RecyclerView就可以。对第二部分RecyclerView Adapter的改动要麻烦一些,因为不同的列表都须要定义单独的Adapter,在这些Adapter中都须要重写getItemCount()。getItemViewType()这些方法。所以不能简单的通过继承RecyclerView Adapter,然后各个列表的Adapter再继承自这个改动后的Adapter来解决。为了实现Adapter的封装,须要实现一个内部的Adapter,然后用这个内部的Adapter包裹外部列表的Adapter来实现。
现分别对这两部分的封装过程进行介绍。
4.1 RecyclerView的封装
对RecyclerView的封装仅仅须要实现一个类继承自RecyclerView,将底部刷新功能对RecyclerView的改动放到这个类中就可以。
演示样例代码例如以下。
public class XRecyclerView extends RecyclerView {
private OnBottomRefreshListener mBottomRefreshListener;
private RecyclerView.OnScrollListener mOnScrollListener;
private boolean mBottomRefreshable;
public XRecyclerView(Context context) {
super(context);
init();
}
public XRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mBottomRefreshListener = null;
mBottomRefreshable = false;
mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (isBottomViewVisible()) {
if (mBottomRefreshListener != null) {
mBottomRefreshListener.onBottomRefresh();
}
}
}
};
}
private