目录
2)onlayout拿到RecyclerView,设置加载更多的监听
1.简介
当页面展示大量相同布局的数据的时候,公司的接口一般都是一页一页的去请求并拿到数据去展示,防止页面因同时加载大量数据出现内存溢出等问题。
下面demo假设每页最多20条数据,第一页数据或者后面加载的某一页数据有可能数量小于20,当小于20的时候,我们就不接着去触发上拉加载更多的监听。主要介绍封装的SwipeRefreshLayout以及它的使用。
顺便说一下,我们上拉加载更多的判定条件有4个
a.recyclerview的状态为RecyclerView.SCROLL_STATE_IDLE
b.recyclerview滑动到了底部
c.recyclerview未在上拉加载或者下拉刷新状态(免得多次加载或者刷新数据的时候去加载数据)
d.手指做了上划的操作,且滑动距离大于android认定的最小滑动距离
demo地址:https://download.csdn.net/download/qq_37321098/10657545
2.自定义的SwipeRefreshLayout
1)全局变量和基础方法
private static final String TAG = "测试";
//正在加载状态
private boolean isLoading = false;
//最小滑动距离,手移动的距离大于这个距离才能拖动控件
private int mScaledTouchSlop;
//加载控件
private RecyclerView mRecyclerView;
//在分发事件的时候处理子控件的触摸事件
private float mDownY, mUpY;
private OnLoadMoreListener mListener;
//是否还有更多数据
private boolean hasMoreDate = true;
public MySwipeRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
//属性设置
public void setInitParams(int bgColor, int proColor) {
//下拉进度的背景颜色
setProgressBackgroundColorSchemeResource(bgColor);
//进度条颜色
setColorSchemeResources(proColor);
}
注意全局变量hasMoreDate就是我们用来判断是否还有更多加载数据判断的依据。如果没有,是不会去触发加载更多的监听。
2)onlayout拿到RecyclerView,设置加载更多的监听
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//判断内部是ListView还是RecyclerView
if (getChildCount() > 0) {
if (getChildAt(0) instanceof RecyclerView) {
mRecyclerView = (RecyclerView) getChildAt(0);
// 设置RecyclerView的滑动监听
setRecyclerViewOnScroll();
}
}
}
//设置RecyclerView的滑动监听
private void setRecyclerViewOnScroll() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
boolean isBottom = isVisBottom(recyclerView);
if (newState == RecyclerView.SCROLL_STATE_IDLE &&
isBottom &&
!isLoading &&
(mDownY - mUpY) >= mScaledTouchSlop) {
Log.e(TAG, "满足条件->加载数据");
if(!hasMoreDate){
Log.e(TAG, "没有更多数据了");
return;
}
if (mListener != null) {
// 加载状态
setLoading(true);
mListener.onLoadMore();
}
}
}
});
}
public static boolean isVisBottom(RecyclerView recyclerView) {
//屏幕中最后一个可见子项的position=总数-1
//当前屏幕所看到的子项个数大于0
//RecyclerView的滑动状态为空闲
if (linearLayoutManager.getChildCount() > 0 &&
linearLayoutManager.findLastVisibleItemPosition() == linearLayoutManager.getItemCount() - 1 &&
recyclerView.getScrollState() == recyclerView.SCROLL_STATE_IDLE) {
Log.e(TAG, "页面展示到底部条目");
return true;
} else {
Log.e(TAG, "页面未展示到底部条目");
return false;
}
}
加载更多的4个依据,开头已经提到过。isVisBottom()函数就是判断Recyclerview是否滑动到了底部,根据最后显示的数据位置是否等于item总数减一(lastVisibleItemPosition 从0起算的),注意这是判断垂直Recyclerview布局情况下滑动到底部的依据。如果是瀑布流布局,我们需要减的数目是你每一行展示内容的个数。还有一种特殊情况,之前也遇到过。如果是Scrollview嵌套的Recyclerview,需要判断的是Scrollview滑动到底部(你会发现Recyclerview滑动的监听不会触发,因为焦点在Scrollview身上),再去加载更多数据,这个可以去百度下如何判断Scrollview滑动到底部,此情况不多见。
3)其余的判断标准
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 移动的起点
mDownY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
// 移动的终点
mUpY = getY();
break;
}
return super.dispatchTouchEvent(ev);
}
public void setLoading(boolean loading) {
isLoading = loading;
//FIXME 可以为recyclerview添加底部布局,通过判断布局type,数据总数+1,加载底部布局
mDownY = 0;
mUpY = 0;
}
//是否还有更多数据
public void setHasMoreDate(boolean hasMoreDate) {
this.hasMoreDate = hasMoreDate;
}
选择在dispatchTouchEvent方法中,拿到down和up在Y轴上位置去判断是否上滑。这个判断还是有必要的,不然会出现到达底部,即使做下滑的操作,都会去触发上拉加载数据的监听。
setLoading()函数就是外部用来设置正在刷新数据或者加载数据的标志,让SwipeRefreshLayout不去触发加载的监听。
setHasMoreDate()函数,就是没有更多数据,或者第一页数据不满分页加载时每一页个数的时候,去调用的方法,设置了之后同样也不会触发加载的监听。
3.Activity中的使用
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MySwipeRefresh mySwipeRefresh;
private TextDateAdapter textDateAdapter;
private LinearLayoutManager linearLayoutManager;
//是否刷新状态中
private boolean isRefreshing = false;
//接口中每一页的数据
private int pageSize = 20;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRefreshView();
}
private void initRefreshView() {
recyclerView = (RecyclerView) findViewById(R.id.rv);
linearLayoutManager = new LinearLayoutManager(MainActivity.this);
recyclerView.setLayoutManager(linearLayoutManager);
mySwipeRefresh = (MySwipeRefresh) findViewById(R.id.refresh);
mySwipeRefresh.setInitParams(android.R.color.white, R.color.colorAccent);
//分割
recyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this,
linearLayoutManager.getOrientation()));
//刷新加载回调
initLoadCallback();
//数据初始加载
initData();
}
private void initLoadCallback() {
// 下拉时触发SwipeRefreshLayout的下拉动画,动画完毕之后就会回调这个方法
mySwipeRefresh.setOnRefreshListener(new MySwipeRefresh.OnRefreshListener() {
@Override
public void onRefresh() {
if (isRefreshing) {
Log.e("测试: ", "下拉刷新中,return");
return;
}
initData();
}
});
// 设置下拉加载更多
mySwipeRefresh.setOnLoadMoreListener(new MySwipeRefresh.OnLoadMoreListener() {
@Override
public void onLoadMore() {
loadMoreData();
}
});
}
private void initData() {
isRefreshing = true;
mySwipeRefresh.setRefreshing(true);
mySwipeRefresh.setLoading(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//FIXME 设置是否还有更多数据,没有的话,不去调用加载的监听
List<String> list = getRefreshDate();
if (list.size() != pageSize) {
mySwipeRefresh.setHasMoreDate(false);
} else {
mySwipeRefresh.setHasMoreDate(true);
}
//初始化数据加载
if (textDateAdapter == null) {
textDateAdapter = new TextDateAdapter(MainActivity.this, list);
recyclerView.setAdapter(textDateAdapter);
} else {
textDateAdapter.refreshData(list);
}
// 收起下拉进度条
if (mySwipeRefresh.isRefreshing()) {
Log.e("测试", "收起下拉进度条");
mySwipeRefresh.setRefreshing(false);
mySwipeRefresh.setLoading(false);
isRefreshing = false;
}
}
}, 1000);
}
private void loadMoreData() {
//FIXME 设置关卡,没有更多数据或者数据个数小于分页加载中每一页的数据,则不去加载更多数据
//FIXME textDateAdapter.addBottomData前,swipererefresh中设置标志,不去触发loadMore的监听
//FIXME 注意在刷新的时候,去变更适配器中的这个标志
//mySwipeRefresh内部设置了loading状态
isRefreshing = true;
mySwipeRefresh.setLoading(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (textDateAdapter != null) {
List<String> list = getUpLoadDate();
//FIXME 标志的设置
if (list.size() != pageSize) {
mySwipeRefresh.setHasMoreDate(false);
}
textDateAdapter.addBottomData(list);
//未刷新状态
mySwipeRefresh.setLoading(false);
isRefreshing = false;
}
}
}, 100);
}
private List<String> getRefreshDate() {
//底部添加的数据集合
List<String> list = new ArrayList<>();
//大于90.模拟后台数据少于分页加载时候每一页的数据
int num = new Random().nextInt(100);
if (num > 80) {
for (int i = 0; i < 10; i++) {
list.add("最后一页数据,少于20:" + i);
}
return list;
}
for (int i = 0; i < 20; i++) {
list.add("分页加载数据:" + i);
}
return list;
}
private List<String> getUpLoadDate() {
//底部添加的数据集合
List<String> list = new ArrayList<>();
//大于90.模拟后台数据少于分页加载时候每一页的数据
int num = new Random().nextInt(100);
if (num > 80) {
for (int i = 0; i < 10; i++) {
list.add("最后一页数据,少于20:" + i);
}
return list;
}
for (int i = 0; i < 20; i++) {
list.add("分页加载数据:" + i);
}
return list;
}
}
4.xml布局使用
<?xml version="1.0" encoding="utf-8"?>
<com.bihucj.mcandroid.view.MySwipeRefresh 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"
android:orientation="vertical"
android:id="@+id/refresh"
tools:context="com.bihucj.mcandroid.ui.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.bihucj.mcandroid.view.MySwipeRefresh>
5.适配器和单行布局
public class TextDateAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> mList;
private Context context;
public TextDateAdapter(Context context, List<String> list) {
this.context = context;
this.mList = list;
}
public void clearData() {
if (mList.size() > 0 && mList != null) {
mList.clear();
notifyDataSetChanged();
}
}
public void addBottomData(List<String> list) {
if (list.size() > 0 && list != null) {
mList.addAll(list);
notifyDataSetChanged();
}
}
public int getListSize() {
return (mList == null) ? 0 : mList.size();
}
public void refreshData(List<String> list) {
if (list.size() > 0 && mList != null) {
mList.clear();
mList.addAll(list);
notifyDataSetChanged();
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.singleitem_text_date, null);
return new ImgsViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
holder.setIsRecyclable(false);
if (holder instanceof ImgsViewHolder) {
TextView tv_text = ((ImgsViewHolder) holder).tv_text;
tv_text.setHeight(50);
tv_text.setGravity(Gravity.CENTER);
tv_text.setText(mList.get(position));
}
}
@Override
public int getItemCount() {
return mList.size();
}
private class ImgsViewHolder extends RecyclerView.ViewHolder {
private TextView tv_text;
public ImgsViewHolder(View itemView) {
super(itemView);
tv_text = itemView.findViewById(R.id.tv_text);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_text"
android:textColor="#00aaff"
android:textSize="@dimen/_15dp"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="asdas"
android:textStyle="bold" />
</LinearLayout>