小序:继《RecyclerView使用攻略(助力篇)》之后,一直没有更新上下拉刷新的功能实现,主要还是受限于个人现有的技术实力,总觉得没有经过实际打磨的,就不敢有上场的自信。虽说现在已经引用到自己的项目上了,但还是得继续跟进优化的。
- 助力篇主要讲的啥
- 刷新篇又涉及哪些
- 下拉刷新 上拉加载更多
- 自定义上拉加载动画
- 自定义空列表提示视图
- 预设属性值设置
- 从我的小栗子经过
- 末尾旁白
助力篇主要讲的啥
- RecyclerView 中 Adapter、ViewHolder 的使用
- RecyclerView 展示分割符、头部与顶部 View (集合)
- 自定义继承 RecyclerView 与属性封装
刷新篇又涉及哪些
此次刷新篇顾名思义就是实现 RecyclerView 的上下刷新及加载更多,与其他绝大多数开发者一样:仅结合官方组件 SwipeRefreshLayout 实现下拉刷新,通过重写函数 OnScrollListener 来监听 RecyclerView 滚动至底部时加载更多的展示:
下拉刷新 上拉加载更多
为了方便回调监听,先定一个统一管理的接口来实现:
- 列表下拉刷新监听
- 列表上拉加载更多监听
- 列表上拉加载更多状态变化监听(方便自定义上拉加载更多动画状态处理)
private URecyclerRefreshListener mListener;
public interface URecyclerRefreshListener {
void onRefresh();
void onLoad(int pageIex);
/**
* @param stats <ul>{@link ULoadingView }
* <li>LOAD_START_加载更多</li>
* <li>LOAD_CLICK_点击加载更多</li>
* <li>LOAD_CLICK_已无更多加载</li></ul>
*/
void loadStats(int stats);
}
下拉刷新引用 SwipeRefreshLayout 实现,同以往一样需要在 .xml 声明。不一样的是,这里为了实现上下拉动作监听的统一处理,只要将实例化的 SwipeRefreshLayout 传递给我们定义的 URecyclerView.setRefreshLayout(); 实现绑定就可以了,为了偷懒还需实例化回调监听。具体看下代码便知。
- 绑定 SwipeRefreshLayout 下拉刷新,URecyclerRefreshListener 回调监听
- 设置下拉控件箭头颜色(可在 .xml 下直接定义)
- 设置下拉动作监听
public void setRefreshLayout(SwipeRefreshLayout refreshLayout,
final URecyclerRefreshListener listener) {
if (null == refreshLayout || null == listener) return;
mRefreshLayout = refreshLayout;
mListener = listener;
if (null != mColors) {
mRefreshLayout.setColorSchemeColors(mColors);
}
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (null == mListener) return;
mPageIndex = 1;
mListener.onRefresh();
}
});
}
而上拉加载更多其实也很简单,通过重写 OnScrollListener 监听判断列表是否滚动至底部即可。借鉴
OnScrollListener mScroll = new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// 得到当前显示的最后一个 item 的 view
View lastChildView = recyclerView.getLayoutManager().getChildAt(
recyclerView.getLayoutManager().getChildCount() - 1);
if (null == lastChildView) return;
// 得到 lastChildView 的bottom 坐标值
int lastChildBottom = mOrientation == VERTICAL ?
lastChildView.getBottom() : lastChildView.getRight();
// 得到 RecyclerView 的底部坐标减去底部 padding 值,也就是显示内容最底部的坐标
int recyclerBottom = mOrientation == VERTICAL ?
(recyclerView.getBottom() - recyclerView.getPaddingBottom()) :
(recyclerView.getRight() - recyclerView.getPaddingRight());
// 通过这个 lastChildView 得到这个 view 当前的 position 值
int lastPosition = recyclerView.getLayoutManager().getPosition(lastChildView);
// 判断 lastChildView 的bottom 值跟 recyclerBottom
// 判断 lastPosition 是不是最后一个 position
// 如果两个条件都满足则说明是真正的滑动到了底部
if (lastChildBottom == recyclerBottom &&
lastPosition == recyclerView.getLayoutManager().getItemCount() - 1) {
}
}
};
余下还得看看接口 URecyclerViewInterface.java 的几个重要函数调用,包括启动数据加载以及结束加载等操作。
/**
* 启动数据刷新加载
*
* @param pageIndex 页码
*/
void refreshLoadStart(int pageIndex);
/**
* 启动数据刷新加载
*/
void refreshLoadStart();
/**
* 结束数据刷新加载,刷新,加载更多成功时调用
*/
void refreshLoadSuccess();
/**
* 结束数据刷新加载,刷新,加载更多成功时调用
*
* @param isLoadMoreEnd true_结束上拉加载(已全部加载完成)
*/
void refreshLoadSuccessIsEnd(boolean isLoadMoreEnd);
/**
* 结束数据刷新加载,刷新,加载更多失败时调用
*/
void refreshLoadFail();
/**
* true_禁用下拉刷新
*/
void disRefresh(boolean isHideRefresh);
/**
* true_禁用上拉加载更多
*/
void disLoadMore(boolean isDisLoadMore);
如果你需要获取当前加载页页码,可以直接通过 URecyclerView 的实例直接获取 mPageIndex,另外为了将获取到的数据区分开来处理(即更新数据与追加数据两种情况)。在成功加载完数据后可直接调用接口 RvDataInterface 下的函数执行数据更新,如下
/**
* 更新数组数据
*
* @param pageIndex 加载页面页码
* @param list
*/
void updateForPage(int pageIndex, List<T> list);
自定义上拉加载动画
在整个上拉加载更多的操作中,其实最受关注的应该是动画的设定。有经验的人应该很快能发现 Demo 当中用到的加载动画源自 jack wang 的 AVLoadingIndicatorView 。其实我也曾考虑过自定义动画,但不经意对于 Airbnb 下开源项目的动画效果赞不绝口,就以此为借口开溜了说。
回到正文上,由于自定义 URecyclerView 的依赖上有 AVLoadingIndicatorView 并已设置为默认(后期会去掉该依赖),故如有需要可直接引用创建动画。
/**
* 设置加载更多视图
*
* @param view
*/
public void setLoadMoreView(View view) {
mLoadMoreView = view;
}
/**
* 设置加载更多视图(默认调用)
*/
public void setLoadMoreView() {
ULoadingView loadingView = new ULoadingView(getContext());
// 确保水平滚动列表加载更多布局与 Item 方向一致
if (mOrientation == HORIZONTAL) {
loadingView.setHorizontalShow();
}
setLoadMoreView(loadingView);
}
自定义空列表提示视图
我们都知道 ListView 有个 setEmptyView(View view) 的函数,当列表处于无数据的环境下显示 view 来提示用户当前状况以及下一步操作。 但是 RecyclerView 需要自己提供,其解决思路也很简单。需要注意的是,只有在空列表提示视图继承自 TextView 的情况下文本提示才会生效,所以如果自定义了其他内容的仍需要自行判断空数据显示内容。
- 设置提示视图与内容(包括网络状态、数据状态)
- Adapter 刷新数据 isEmpty() == true 显示,否则隐藏(首次刷新前默认不需要判断显示)
- 判断是否属于网络异常引起的问题,否则提示重新操作
public void setEmptyView(View emptyView) {
isShowEmptyHide = true;
if (!isURecyclerVAdapter()) {
this.mEmptyView = emptyView;
} else {
BaseRVAdapter adapter = ((BaseRVAdapter) getAdapter());
adapter.mEmptyView = emptyView;
adapter.mEmptyView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 下拉刷新的不需要点击重新加载更多
if (null == mListener || null != mRefreshLayout) return;
mListener.onRefresh();
}
});
}
}
/**
* 默认调用
*/
public void setEmptyView() {
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setTextSize(18);
textView.setTextColor(ULoadingView.sDefaultColor);
textView.setPadding(0, 0, 0, 80);
textView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
setEmptyView(textView);
}
/**
* 设置提示语
*
* @param networkFailStr 网络异常时提示
* @param dataEmptyStr 空数据列表提示
*/
public void setEmptyHide(String networkFailStr, String dataEmptyStr) {
mNetworkFailStr = networkFailStr;
mDataEmptyStr = dataEmptyStr;
if (isURecyclerVAdapter()) {
BaseRVAdapter adapter = (BaseRVAdapter) getAdapter();
adapter.mNetworkFailStr = networkFailStr;
adapter.mDataEmptyStr = dataEmptyStr;
}
}
/**
* 设置提示语
*
* @param networkFailId 网络异常时提示
* @param dataEmptyId 空数据列表提示
*/
public void setEmptyHide(int networkFailId, int dataEmptyId) {
Resources resources = getContext().getResources();
setEmptyHide(resources.getString(networkFailId), resources.getString(dataEmptyId));
}
预设属性值设置
与助力篇一样,为了省去声明 RecycleView 而需要在类当中加入一系列的属性预设。我们同样为以上功能提供了力所能及的自定义属性引用。具体如下
Name | Format | Function |
colors | reference | 下拉刷新颜色变换数组array,默认无 |
isFixSize | boolean | 是否固定列表项视图大小,默认固定 |
loadMoreType | enum | auto主动加载更多,autoHide主动加载更多并隐藏,click点击加载更多,clickHide点击加载更多并隐藏,默认不提供加载更多 |
emptyType | enum | none不显示空数据提示,text显示空数据文本提示,默认none |
spanCount | integer | 网格布局显示列数,默认2列 |
divider_marginLeft | reference | 分隔符对应资源Drawable |
divider_marginTop | dimension | 分隔符左边距,默认0 |
divider_marginRight | dimension | 分隔符右边距,默认0 |
divider_marginTop | dimension | 分隔符顶部边距,默认0 |
divider_marginBottom | dimension | 分隔符底部边距,默认0 |
divider_background | color | 分隔符边距底色,默认无 |
textNetworkFail | string/reference | 网络错误提示语 |
textDataEmpty | string/reference | 空数据内容提示语 |
layoutManagerType | enum | list列表类型,grid网格类型,staggeredGrid,瀑布流类型 |
orientation | enum | vertical垂直方向类型,horizontal水平方向列表 |
从我的小栗子经过
有时候看一堆内容后很容易觉得蒙圈甚至有些许倦意,所以盛上栗子(实际上涉及的代码篇幅感觉太多,就不一一贴出来了)。
arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer-array name="refresh_colors">
<item>@color/colorPrimary</item>
<item>@color/origin</item>
<item>@color/colorAccent</item>
</integer-array>
</resources>
strings.xml
<resources>
<string name="app_name">URecycleView</string>
<string name="text_network_fail">网络不给力吖~</string>
<string name="text_data_empty">尝试下拉找回你的数据吧~</string>
</resources>
fragment_rv.layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:urv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/main_sw_ly"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.urun.undroidlib.view.URecyclerView
android:id="@+id/main_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
urv:colors="@array/refresh_colors"
urv:divider_background="@color/white"
urv:divider_drawable="@drawable/recycle_view_divider"
urv:divider_marginLeft="16dp"
urv:divider_marginRight="16dp"
urv:layoutManagerType="list"
urv:emptyType="text"
urv:loadMoreType="click"
urv:textDataEmpty="@string/text_data_empty"
urv:textNetworkFail="@string/text_network_fail"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
RecycleVFragment.java
public class RecycleVFragment extends Fragment {
private URecyclerView mMainRv;
private SwipeRefreshLayout mRefreshLayout;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_rv, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mMainRv = (URecyclerView) view.findViewById(R.id.main_rv);
mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.main_sw_ly);
initRecycleView();
}
/**
* 实例化列表视图
*/
private void initRecycleView() {
RecycleVAdapter adapter = new RecycleVAdapter(getActivity(), new ArrayList());
adapter.addItemClickListener(new BaseRVAdapter.OnItemClickListener() {
@Override
public void itemClick(int position) {
}
});
mMainRv.setAdapter(adapter);
mMainRv.setRefreshLayout(mRefreshLayout, mRefresh);
}
/**
* 模拟接口请求操作
*/
private void loadData(int pageIndex) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
}, 2000);
}
URecyclerView.URecyclerRefreshListener mRefresh = new URecyclerView.URecyclerRefreshListener() {
@Override
public void onRefresh() {
loadData(1);
}
@Override
public void onLoad(int pageIex) {
loadData(pageIex);
}
@Override
public void loadStats(int stats) {
}
};
}