Android 下拉刷新框架实现

article/2025/10/26 18:04:31

前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但这些demo的质量参差不齐,用户体验也不好,接口设计也不行。最张没办法,终于忍不了了,自己就写了一个下拉刷新的框架,这个框架是一个通用的框架,效果和设计感觉都还不错,现在分享给各位看官。

致谢:

1. 感谢lk6233160同学提出的问题,旋转View时调用setRotation方法只能是在API Level11(3.0)以上才能用,这个问题的解决办法是给ImageView设置一个Matrix,把Matrix上面作用一个旋转矩阵,但是如果不是ImageView的话,可能实现起来比较麻烦,再次谢谢lk6233160同学。

2. 谢谢如毛毛风提出的问题,向下滑动后,再向上滑动到头,只能再松手后才能再次下拉。这个问题的回复请参考评论。

技术交流群:

QQ:197990971(人员已满)


1. 关于下拉刷新

下拉刷新这种用户交互最早由twitter创始人洛伦•布里切特(Loren Brichter)发明, 有理论认为,下拉刷新是一种适用于按照从新到旧的时间顺序排列feeds的应用,在这种应用场景中看完旧的内容时,用户会很自然地下拉查找更新的内容,因此下拉刷新就显得非常合理。大家可以参考这篇文章: 有趣的下拉刷新,下面我贴出一个有趣的下拉刷新的案例。

图一、有趣的下拉刷新案例(一)


图一、有趣的下拉刷新案例(二)


2. 实现原理

上面这些例子,外观做得再好看,他的本质上都一样,那就是一个下拉刷新控件通常由以下几部分组成:
【1】Header
Header通常有下拉箭头,文字,进度条等元素,根据下拉的距离来改变它的状态,从而显示不同的样式
【2】Content
这部分是内容区域,网上有很多例子都是直接在ListView里面添加Header,但这就有局限性,因为好多情况下并不一定是用ListView来显示数据。我们把要显示内容的View放置在我们的一个容器中,如果你想实现一个用ListView显示数据的下拉刷新,你需要创建一个ListView旋转到我的容器中。我们处理这个容器的事件(down, move, up),如果向下拉,则把整个布局向下滑动,从而把header显示出来。
【3】Footer
Footer可以用来显示向上拉的箭头,自动加载更多的进度条等。

以上三部分总结的说来,就是如下图所示的这种布局结构:
图三,下拉刷新的布局结构

关于上图,需要说明几点:
1、这个布局扩展于 LinearLayout,垂直排列
2、从上到下的顺序是:Header, Content, Footer
3、Content填充满父控件,通过设置top, bottom的padding来使Header和Footer不可见,也就是让它超出屏幕外
4、下拉时,调用scrollTo方法来将整个布局向下滑动,从而把Header显示出来,上拉正好与下拉相反。
5、派生类需要实现的是:将Content View填充到父容器中,比如,如果你要使用的话,那么你需要把ListView, ScrollView, WebView等添加到容器中。
6、上图中的红色区域就是屏的大小(严格来说,这里说屏幕大小并不准确,应该说成内容区域更加准确)

3. 具体实现

明白了实现原理与过程,我们尝试来具体实现,首先,为了以后更好地扩展,设计更加合理,我们把下拉刷新的功能抽象成一个接口:

1、IPullToRefresh<T extends View>

它具体的定义方法如下:
public interface IPullToRefresh<T extends View> {public void setPullRefreshEnabled(boolean pullRefreshEnabled);public void setPullLoadEnabled(boolean pullLoadEnabled);public void setScrollLoadEnabled(boolean scrollLoadEnabled);public boolean isPullRefreshEnabled();public boolean isPullLoadEnabled();public boolean isScrollLoadEnabled();public void setOnRefreshListener(OnRefreshListener<T> refreshListener);public void onPullDownRefreshComplete();public void onPullUpRefreshComplete();public T getRefreshableView();public LoadingLayout getHeaderLoadingLayout();public LoadingLayout getFooterLoadingLayout();public void setLastUpdatedLabel(CharSequence label);
}
这个接口是一个泛型的,它接受View的派生类, 因为要放到我们的容器中的不就是一个View吗?

2、PullToRefreshBase<T extends View>
这个类实现了IPullToRefresh接口,它是从LinearLayout继承过来,作为下拉刷新的一个 抽象基类,如果你想实现ListView的下拉刷新,只需要扩展这个类,实现一些必要的方法就可以了。这个类的职责主要有以下几点:
  • 处理onInterceptTouchEvent()和onTouchEvent()中的事件当内容的View(比如ListView)正如处于最顶部,此时再向下拉,我们必须截断事件,然后move事件就会把后续的事件传递到onTouchEvent()方法中,然后再在这个方法中,我们根据move的距离再进行scroll整个View。
  • 负责创建Header、Footer和Content View在构造方法中调用方法去创建这三个部分的View,派生类可以重写这些方法,以提供不同式样的Header和Footer,它会调用createHeaderLoadingLayout和createFooterLoadingLayout方法来创建Header和Footer创建Content View的方法是一个抽象方法,必须让派生类来实现,返回一个非null的View,然后容器再把这个View添加到自己里面。
  • 设置各种状态:这里面有很多状态,如下拉、上拉、刷新、加载中、释放等,它会根据用户拉动的距离来更改状态,状态的改变,它也会把Header和Footer的状态改变,然后Header和Footer会根据状态去显示相应的界面式样。
3、PullToRefreshBase<T extends View>继承关系
这里我实现了三个下拉刷新的派生类,分别是ListView、ScrollView、WebView三个,它们的继承关系如下:

图四、PullToRefreshBase类的继承关系

关于PullToRefreshBase类及其派和类,有几点需要说明:
  • 对于ListView,ScrollView,WebView这三种情况,他们是否滑动到最顶部或是最底部的实现是不一样的,所以,在PullToRefreshBase类中需要调用两个抽象方法来判断当前的位置是否在顶部或底部,而其派生类必须要实现这两个方法。比如对于ListView,它滑动到最顶部的条件就是第一个child完全可见并且first postion是0。这两个抽象方法是:
    /*** 判断刷新的View是否滑动到顶部* * @return true表示已经滑动到顶部,否则false*/protected abstract boolean isReadyForPullDown();/*** 判断刷新的View是否滑动到底* * @return true表示已经滑动到底部,否则false*/protected abstract boolean isReadyForPullUp();
  • 创建可下拉刷新的View(也就是content view)的抽象方法是
    /*** 创建可以刷新的View* * @param context context* @param attrs 属性* @return View*/protected abstract T createRefreshableView(Context context, AttributeSet attrs);
4、LoadingLayout
LoadingLayout是刷新Layout的一个抽象,它是一个抽象基类。Header和Footer都扩展于这个类。这类抽象类,提供了两个抽象方法:
  • getContentSize
这个方法返回当前这个刷新Layout的大小,通常返回的是布局的高度,为了以后可以扩展为水平拉动,所以方法名字没有取成getLayoutHeight()之类的,这个返回值,将会作为松手后是否可以刷新的临界值,如果下拉的偏移值大于这个值,就认为可以刷新,否则不刷新,这个方法必须由派生类来实现。
  • setState
这个方法用来设置当前刷新Layout的状态,PullToRefreshBase类会调用这个方法,当进入下拉,松手等动作时,都会调用这个方法,派生类里面只需要根据这些状态实现不同的界面显示,如下拉状态时,就显示出箭头,刷新状态时,就显示loading的图标。
可能的状态值有: RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA

LoadingLayout及其派生类的继承关系如下图所示:

图五、LoadingLayout及其派生类的类图

我们可以随意地制定自己的Header和Footer,我们也可以实现如图一和图二中显示的各种下拉刷新案例中的Header和Footer,只要重写上述两个方法getContentSize()和setState()就行了。HeaderLoadingLayout,它默认是显示箭头式样的布局,而RotateLoadingLayout则是显示一个旋转图标的式样。

5、事件处理
我们必须重写PullToRefreshBase类的两个事件相关的方法 onInterceptTouchEvent()和onTouchEvent()方法。由于ListView,ScrollView,WebView它们是放到PullToRefreshBase内部的,所在事件先是传递到PullToRefreshBase#onInterceptTouchEvent()方法中,所以我们应该在这个方法中去处理ACTION_MOVE事件,判断如果当前ListView,ScrollView,WebView是否在最顶部或最底部,如果是,则开始截断事件,一旦事件被截断,后续的事件就会传递到PullToRefreshBase#onInterceptTouchEvent()方法中,我们再在ACTION_MOVE事件中去移动整个布局,从而实现下拉或上拉动作。

6、滚动布局(scrollTo)
如图三的布局结构可知,默认情况下Header和Footer是放置在Content View的最上面和最下面,通过设置padding来让他跑到屏幕外面去了,如果我们将整个布局向下滚动(scrollTo)一定距离,那么Header就会被显示出来,基于这种情况,所以在我的实现中,最终我是调用 scrollTo来实现下拉动作的。

总的说来,实现的重要的点就这些,具体的一些细节在实现在会碰到很多,可以参考代码。

4. 如何使用

使用下拉刷新的代码如下
@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mPullListView = new PullToRefreshListView(this);setContentView(mPullListView);// 上拉加载不可用mPullListView.setPullLoadEnabled(false);// 滚动到底自动加载可用mPullListView.setScrollLoadEnabled(true);mCurIndex = mLoadDataCount;mListItems = new LinkedList<String>();mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);// 得到实际的ListViewmListView = mPullListView.getRefreshableView();// 绑定数据mListView.setAdapter(mAdapter);       // 设置下拉刷新的listenermPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {@Overridepublic void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {mIsStart = true;new GetDataTask().execute();}@Overridepublic void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {mIsStart = false;new GetDataTask().execute();}});setLastUpdateTime();// 自动刷新mPullListView.doPullRefreshing(true, 500);}
这是初始化一个下拉刷新的布局,并且调用setContentView来设置到Activity中。
在下拉刷新完成后,我们可以调用 onPullDownRefreshComplete()和onPullUpRefreshComplete()方法来停止刷新和加载

5. 运行效果

这里列出了demo的运行效果图。

图六、ListView下拉刷新,注意Header和Footer的样式


图七、WebView和ScrollView的下拉刷新效果图


6. 源码下载

实现这个下拉刷新的框架,并不是我的原创,我也是参考了很多开源的,把我认为比较好的东西借鉴过来,从而形成我的东西,我主要是参考了下面这个demo:
https://github.com/chrisbanes/Android-PullToRefresh 这个demo写得不错,不过他这个太复杂了,我们都知道,一旦复杂了,万一我们要添加一些需要,自然也要费劲一些,我其实就是把他的简化再简化,以满足我们自己的需要。

源码下载请猛点我

转载请说明出处
http://blog.csdn.net/leehong2005/article/details/12567757 
谢谢!!!


7. Bug修复


已知bug修复情况如下,发现了代码bug的看官也可以给我反馈,谢谢~~~

1,对于ListView的下拉刷新,当启用滚动到底自动加载时,如果footer由隐藏变为显示时,出现显示异常的情况
这个问题已经修复了,修正的代码如下:
  • PullToRefreshListView#setScrollLoadEnabled方法,修正后的代码如下:
    @Overridepublic void setScrollLoadEnabled(boolean scrollLoadEnabled) {if (isScrollLoadEnabled() == scrollLoadEnabled) {return;}super.setScrollLoadEnabled(scrollLoadEnabled);if (scrollLoadEnabled) {// 设置Footerif (null == mLoadMoreFooterLayout) {mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());mListView.addFooterView(mLoadMoreFooterLayout, null, false);}mLoadMoreFooterLayout.show(true);} else {if (null != mLoadMoreFooterLayout) {mLoadMoreFooterLayout.show(false);}}}
  • LoadingLayout#show方法,修正后的代码如下:
    /*** 显示或隐藏这个布局* * @param show flag*/public void show(boolean show) {// If is showing, do nothing.if (show == (View.VISIBLE == getVisibility())) {return;}ViewGroup.LayoutParams params = mContainer.getLayoutParams();if (null != params) {if (show) {params.height = ViewGroup.LayoutParams.WRAP_CONTENT;} else {params.height = 0;}requestLayout();setVisibility(show ? View.VISIBLE : View.INVISIBLE);}}
在更改LayoutParameter后,调用requestLayout()方法。
  • 图片旋转兼容2.x系统
我之前想的是这个只需要兼容3.x以上的系统,但发现有很多网友在使用过程中遇到过兼容性问题,这次抽空将这个兼容性一并实现了。
       onPull的修改如下:
    @Overridepublic void onPull(float scale) {if (null == mRotationHelper) {mRotationHelper = new ImageViewRotationHelper(mArrowImageView);}float angle = scale * 180f; // SUPPRESS CHECKSTYLEmRotationHelper.setRotation(angle);}

ImageViewRotationHelper主要的作用就是实现了ImageView的旋转功能,内部作了版本的区分,实现代码如下:

/*** The image view rotation helper* * @author lihong06* @since 2014-5-2*/static class ImageViewRotationHelper {/** The imageview */private final ImageView mImageView;/** The matrix */private Matrix mMatrix;/** Pivot X */private float mRotationPivotX;/** Pivot Y */private float mRotationPivotY;/*** The constructor method.* * @param imageView the image view*/public ImageViewRotationHelper(ImageView imageView) {mImageView = imageView;}/*** Sets the degrees that the view is rotated around the pivot point. Increasing values* result in clockwise rotation.** @param rotation The degrees of rotation.** @see #getRotation()* @see #getPivotX()* @see #getPivotY()* @see #setRotationX(float)* @see #setRotationY(float)** @attr ref android.R.styleable#View_rotation*/public void setRotation(float rotation) {if (APIUtils.hasHoneycomb()) {mImageView.setRotation(rotation);} else {if (null == mMatrix) {mMatrix = new Matrix();// 计算旋转的中心点Drawable imageDrawable = mImageView.getDrawable();if (null != imageDrawable) {mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);}}mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);mImageView.setImageMatrix(mMatrix);}}}

最核心的就是,如果在2.x的版本上,旋转ImageView使用Matrix。

  • PullToRefreshBase构造方法兼容2.x
在三个参数的构造方法声明如下标注:
@SuppressLint("NewApi")
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)



大家如果还有什么问题,欢迎留言~~~



http://chatgpt.dhexx.cn/article/2uXsfuhA.shtml

相关文章

Android中实现下拉刷新

需求&#xff1a;项目中的消息列表界面要求实现类似sina微博的下拉刷新&#xff1b; 思路&#xff1a;一般的消息列表为ListView类型&#xff0c;将list加载到adapter中&#xff0c;再将adapter加载到ListView中&#xff0c;从而实现消息列表的展示。而下拉刷新要求给消息列表…

微信小程序下拉刷新

一、如何设置微信小程序所有页面都可以下拉刷新呢&#xff1f; 1、在app.json的"window"中进行配置 &#xff08;1&#xff09;把"backgroundTextStyle":“light"改为"backgroundTextStyle”:“dark” &#xff08;2&#xff09;添加"enab…

下拉刷新上拉加载

目录 原理实现效果 原理 想必使用过微信开发工具的应该都接触过上拉加载下拉刷新配置。 原理呢就是通过根据当前刚开始触碰的屏幕垂直y轴距离和滑动时所触碰垂直y轴距离&#xff0c;从而来判断是上拉&#xff0c;下拉。 实现 使用的vue2 封装的组件&#xff0c;js大致思路是…

Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能

转载请注明出处&#xff1a;http://blog.csdn.net/guolin_blog/article/details/9255575 最近项目中需要用到ListView下拉刷新的功能&#xff0c;一开始想图省事&#xff0c;在网上直接找一个现成的&#xff0c;可是尝试了网上多个版本的下拉刷新之后发现效果都不怎么理想。有些…

【Demo】教你实现下拉刷新

前言 第三方库很常见&#xff0c;我们开发需求的时候经常会用到下拉刷新组件&#xff0c;如果要我们自己实现下拉刷新该如何实现尼&#xff1f; 效果 实现原理 1、监听 touchstart事件记录初始startY 2、监听 touchmove事件 e.touches[0].pageY - startY得到 deltaY&#xf…

如何实现上拉加载,下拉刷新

答&#xff1a;下拉刷新和上拉加载这两种交互⽅式通常出现在移动端中 本质上等同于PC⽹⻚中的分⻚&#xff0c;只是交互形式不同 开源社区也有很多优秀的解决⽅案&#xff0c;如 iscroll 、 better-scroll 、 pulltorefresh.js 库等等 这些第三⽅库使⽤起来⾮常便捷 我们通…

jira使用教程管理项目

添加工作流 1.点击项目设置 2.点击工作流&#xff0c;然后点击切换方案 3.进入切换方案界面之后&#xff0c;点击工作流&#xff0c;然后点击添加工作流 4.输入工作流名称&#xff0c;点击添加 5.编辑工作流 可以导出工作流供别人使用 编辑之前添加的工作流 提交bug

今日记录:JIRA使用指南

JIRA流程与使用指南 学习总结&#xff1a; 什么是JIRA&#xff1f;为什么要用任务管理工具&#xff1f;JIRA基础流程 ① 什么是JIRA&#xff1f; JIRA是一个项目与事务管理工具&#xff0c;被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪和敏捷管理等工作领…

JIRA-使用教程_概念

博客概要 JIRA是一个非常强大的项目与事务跟踪工具&#xff0c;博主在具体工作使用中对它爱不释手 &#xff0c;发现它功能全面、配置灵活、扩展丰富…反正优点一大堆&#xff01;好东西嘛就要拿出来&#xff0c;大家一起分享~本博文先简单介绍JIRA相关的基本信息。 文章目录 博…

Confluence+Jira使用

Jira过滤器的使用方式&#xff1a; project MCU AND issuetype 测试用例 AND component GPIO ORDER BY summary ASC实例&#xff1a;

jira使用教程 一(从官方文档理解)

JIRA官方说明 JIRA使用教程 blog QA: jira 如何关联到Gerrit Reviews JIRA Concepts - Issues Aim JIRA tracks issues, which can be bugs, feature requests, or any other tasks you want to track. JIRA可以跟踪问题&#xff0c;可以是bug、特性请求或任何其他你想跟踪…

JIRA-使用教程_问题单-新建

博客概要 简单分享新增并查看一个问题单的步骤~ 文章目录 博客概要《JIRA-使用教程》_总目录问题单-新建1.进入项目2.新建唤起3.填写新建弹窗4.查看问题 总结 《JIRA-使用教程》_总目录 文章超链接&#xff1a;https://blog.csdn.net/qq_41386332/article/details/108865809 …

项目管理工具——Jira使用和配置

摘要 链接&#xff1a;https://pan.baidu.com/s/1_PgOuOWsS1lnHIIyI0y4pA 提取码&#xff1a;v0dy 本博文将介绍在软件开发中的常用的软件管理工具。Jira是Atlassian公司出品的一款事务管理软件&#xff08;缺陷管理类的软件&#xff09;。无论是“需求”&#xff0c;还是“…

命令进入mysql创建jira_JIRA使用教程:连接数据库—MySQL_MySQL

本文主要介绍如何连接JIRA到mysql数据库。 首先 查看MySQL的版本是否支持&#xff0c;查阅 支持的平台 。 如果是转移JIRA到另一台服务器&#xff0c;先导出数据为XML备份&#xff0c;然后将旧数据库中的数据传输到新的数据库。 如果你打算用同一个MySQL服务器安装Confluence和…

jira oracle安装,JIRA使用教程:连接数据库—Oracle

本文主要介绍连接JIRA到Oracle数据库。 首先 检查Oracle的版本是否支持&#xff0c;详见支持的平台。 如果是转移JIRA到另一台服务器&#xff0c;先导出数据为XML备份&#xff0c;然后将旧数据库中的数据传输到新的数据库。 在开始前关闭JIRA&#xff0c;除非你正在运行Setup W…

命令进入mysql创建jira_JIRA使用教程:连接数据库―MySQL

本文主要介绍如何连接JIRA到mysql数据库。 首先 查看MySQL的版本是否支持&#xff0c;查阅 支持的平台 。 如果是转移JIRA到另一台服务器&#xff0c;先导出数据为XML备份&#xff0c;然后将旧数据库中的数据传输到新的数据库。 如果你打算用同一个MySQL服务器安装Confluence和…

Jira使用浅谈篇一

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

jira软件 linux 安装,JIRA使用教程:在Linux上安装JIRA

本指南主要介绍如何使用“Linux Installer”在Linux上安装一个新的JIRA。如果要升级请参阅JIRA升级指南。 申请试用请咨询在线客服。 注意&#xff1a;一些反病毒或其他互联网安全工具可能会干扰JIRA的安装过程&#xff0c;并阻止安装过程成功完成。为了避免遇到这样的情况&…

jira的详细使用教程

jira的详细使用教程,1,Jira- 是一款缺陷管理软件工具,用其进行缺陷的管理和跟踪。(是B/S架构的软件) 2,主要涉及的角色: 3,具体工作流 添加工作流 1.点击项目设置 2.点击工作流,然后点击切换方案 3.进入切换

命令进入mysql创建jira_JIRA使用教程

连接数据库—MySQL 本文主要介绍如何连接JIRA到MySQL数据库。 相关资料推荐&#xff1a; 首先查看MySQL的版本是否支持&#xff0c;查阅支持的平台。 如果是转移JIRA到另一台服务器&#xff0c;先导出数据为XML备份&#xff0c;然后将旧数据库中的数据传输到新的数据库。 如果你…