您的位置:首页 > 移动开发 > Android开发

SwipeRefreshLayout + ListView 实现仿简书 下拉刷新 与 上拉加载

2017-11-05 15:05 1291 查看
Android 5.0 之后,谷歌官方提供了一款下拉刷新的控件 SwipeRefreshLayout。国外的软件中,YouTube、Google+ 等应用基本都采用该统一的下拉方式。不过很遗憾,该控制不支持上拉加载,而在 Android 的列表数据展示中,很少有不需要做分页加载的。


本次以 简书 的刷新加载为例,用SwipeRefreshLayout + ListView 实现 下拉刷新 和 上拉加载。

首先,肯定是需要自定义控件 继承自 SwipeRefreshLayout,然后为其添加进去 加载的监听器。其中关键的操作的是:什么时候才可以调用 加载事件 执行 上拉加载的操作。

分析:能够执行上拉加载操作的条件

1. 用户在执行上拉的滑动操作;

2. 数据量超过一个屏幕的内容;

3. 此时并没有在执行下拉刷新操作;

4. 此时也没有在执行上拉加载操作;

5. 内容滑动到最底部;

整体的操作就是围绕这5个条件来实现的,那么如何判断上面5个条件是否成立?

1、判断用户执行的是上滑操作:

只要可以记录用户按在屏幕上时的Y坐标,以及松开手指的Y坐标即可判断是上拉还是下拉操作;所以实现 View 的 disPatchTouchEvent() 方法,记录ACTION_DOWN 事件时的 Y 坐标,以及 ACTION_UP 事件的 Y 坐标即可,然后判断差值是正数还是负数(为了准确性更高一点还可以加上判断是不是大于设备可监听到的最小滑动距离);

关键代码如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 移动的起点
mDownY = ev.getY();
break;
// 用滑动事件来判断是否可以加载不妥,松手后因为惯性继续滑动是不再执行该事件的ACTION_MOVE的
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
// 移动的终点
mUpY = getY();
break;
default:
break;
}
// 加载的时候,设置该控件不可用,则加载的时候不能刷新
if (isLoading) {
setEnabled(false);
} else {
setEnabled(true);
}
return super.dispatchTouchEvent(ev);
}


另外,上面的方法中还有一个条件表达式,当加载的时候设置该控件不可用,那么就没法在加载的时候再执行刷新操作,然后不是加载状态重新设置该控件可以下拉刷新。

2、 判断数据量是否有超过一个屏幕的内容:

由于是 ListView 实现数据的显示,并且 ListView 的 scroll 方法中正好有两个参数: visibleItemCount 和 totalItemCount。所以只要判断 总共的 Item 数量大于 可见的 Item 数量是不是为 true 即可。

关键代码如下:

ListView mListView = (ListView) getChildAt(0);


因为 下拉刷新、上拉加载是SwipeRefreshLayout 嵌套 ListView 实现的,所以在重写的 SwipeRefreshLayout 控件的 onLayout() 方法中,可以通过上面的代码拿到 ListView 对象,

然后设置 ListView 的滑动监听事件,实现 onScroll() 方法:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 每次滚动开始 重置 条件为 false
condition2 = false;
if (totalItemCount > visibleItemCount) {
// 是否可以加载 条件2,ListView的数据量超过一屏幕
condition2 = true;
}
}


3、 此时并没有在执行下拉刷新的操作:

由于控件继承自 SwipeRefreshLayout,所以可以调用 isRefresh() 方法判断现在是不是在执行下拉刷新;

关键代码如下:

// 3,正在刷新时不能加载
boolean condition3 = false;
if (!isRefreshing()) {
condition3 = true;
}


4、 此时并没有在执行上拉加载的操作:

定义一个成员变量 boolean isLoading; 用于记录现在是不是处于加载状态,所以判断条件4 只要判定 isLoading 是否为 true。

5、 判断现在是不是滑动到最底部:

刚开始我先想到的是判断最后可见的Item 的 Position 是不是等于 ListView 的数量,所以代码如下:

if (mListView.getLastVisiblePosition() == mListView.getAdapter().getCount() - 1) {
condition5 = true;
}


但是后面测试发下有点问题,虽然是滑动到了最后一个 Item,但是只要最后一个 Item的顶部漏出来,就会为 true,而不是滑动到最后一个 Item 的底部位置。后来在网上找到一种方式,同样是监听 ListView 的 onScroll() 滑动事件来判断是不是滑到了最底部。代码如下:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 每次滚动开始 重置 条件为 false
condition5 = false;
/*
用于 判断 ListView 滑动到 最后一条 的 底部, http://blog.csdn.net/wangbaochu/article/details/50630371 */
if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
View lastVisibleItemView = mListView.getChildAt(mListView.getChildCount() - 1);
if (lastVisibleItemView != null && lastVisibleItemView.getBottom() == mListView.getHeight()) {
condition5 = true;
}
}
}


综合以上内容,可以上拉加载的条件都完成了,所以,当条件具备之后要执行什么操作呢?当然是添加 FooterView,然后调用加载方法啦!这里有一个 需要注意的地方是,加载时显示 FooterView,加载完之后 隐藏 FooterView;虽然FooterView 也是一个 View 对象,却不能直接调用 setVisibility() 来控制 FooterView 的显示和隐藏,而是调用 ListView 的 addFooterView() 方法显示底部布局,然后调用 ListView 的 removeFooterView() 来移除底部布局;如果一定要用 setVisibility() 来控制,也可以在 FooterView 外面嵌套上一个 View,用外层的 View 调用 setVisibility() 控制底部布局的显示隐藏。

另外,如果只是将 mFooterView 添加进 ListView,即使你将 ListView 下滑到底部,他也不会直接显示出来的,而需要你在加载完之前在上拉一次才行,所以,我在mFooterView 添入 ListView 时,然后调用 ListView 的 smoothScrollToPosition() 滑动到最后一项,即 mFooterView,所以效果就是滑动到 ListView的最后一项的底部时,底部布局添加进去,并且自动滚进屏幕。

public void setLoading(boolean loading) {
// 修改当前的状态
isLoading = loading;
if (isLoading) {
// 添加布局并且显示出来
mListView.addFooterView(mFooterView);
if (mListView.getAdapter() != null) {
// 用于上面添加完 FooterView 之后,将 其 滑动出屏幕
mListView.smoothScrollToPosition(
mListView.getAdapter().getCount() - 1);
}
} else {
// 隐藏布局
mListView.removeFooterView(mFooterView);

// 重置滑动的坐标
mDownY = 0;
mUpY = 0;
}
}


下面通过 GIF 来看一下效果(为了效果更加明显,我给刷新和加载都加上了4秒钟的延时):

图1:下拉刷新时不能加载; 图2:上拉加载时不能刷新; 图3:内容不足一屏不能加载


.

.


使用:

// 刷新事件
swipeFlushView.setOnFlushListener(new SwipeFlushView.OnFlushListener() {
@Override
public void onFlush() {
pageNum = 1;
getDataList();
}
});

// 加载事件
swipeFlushView.setOnLoadListener(new  SwipeFlushView.OnLoadListener() {
@Override
public void onLoad() {
pageNum++;
getDataList();
}
});


刷新和加载完之后要停止请调用 setFlushing(false); 和 setLoading(false); 而不是调用 setRefresh(false)来停止刷新。

另外,重新给该控件添加了几个自定义的属性:

autoFlushing: 是否要自动刷新

autoLoading: 是否内容滑到最底部直接显示FooterView进行加载

flushingEnabled: 是否禁止掉下拉刷新

loadingEnabled: 是否禁止掉上拉加载

这四个属性同样对应的4个set()方法,以供在 Java 里面调用

详细的代码与使用示例请转 GitHub; 最后贴上项目地址https://github.com/PenoAnd/SwipeFlushView
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐