尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

SmartRefreshLayout 自定义 Header 和 Footer 完全指南:从接口原理到实战实现

发布时间:2026/9/30 1:57:47

资讯中心
01
ARTICLE

SmartRefreshLayout 自定义 Header 和 Footer 完全指南:从接口原理到实战实现

SmartRefreshLayout 自定义 Header 和 Footer 完全指南:从接口原理到实战实现
移动开发UI组件【免费下载链接】SmartRefreshLayout下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScrollAndroid智能下拉刷新框架支持越界回弹、越界拖动具有极强的扩展性集成了几十种炫酷的Header和 Footer。项目地址https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout点击查看免费下载本篇技术指南聚焦 SmartRefreshLayout 框架的自定义扩展能力系统讲解下拉刷新/上拉加载组件的接口体系、变换方式、状态机与核心代码实现。读者学完后将能够从零编写属于自己的卡通形象 Header如美团、饿了么、天猫风格的吉祥物下拉刷新并理解框架底层是如何把滑动事件、回弹动画与业务视图解耦的。把情感化设计的 APP 卡通形象植入刷新组件稍微大一点的互联网公司都有属于自己的企业或 App 的卡通吉祥物。这些品牌元素都是 App 设计师发挥创意设计的好素材也是需要我们巧妙植入到 App UI 界面设计当中去的最重要的元素。比如美团、饿了吗、天猫把吉祥物做成下拉刷新 Header既能延续品牌调性又能给用户带来惊喜感。而 SmartRefreshLayout 的设计目标正是让你在一个稳定强大的下拉布局中实现自己项目需求的 Header不用去关心滑动事件处理不用关心子控件的回弹和滚动边界只需关注自己真正想要的 Header 的样子和动画。注意本文内容都为基础和原理真正实现起来还要写很多代码。不过框架本身已经内置了十几款风格各异的 Headerrefresh-header 目录下的BezierCircleHeader、DeliveryHeader、FlyRefreshHeader、FunGameBattleCityHeader、FunGameHitBlockHeader、PhoenixHeader、StoreHouseHeader、TaurusHeader、WaterDropHeader、WaveSwipeHeader等你可以直接参考它们来加速自己的自定义开发。框架体系结构在学习使用框架的自定义功能之前有必要先了解框架的体系和结构。从源码分层看refresh-layout-kernel是核心模块整个框架由以下四个角色协同工作RefreshLayout下拉的基本功能包括布局测量、滑动事件处理、参数设定等等。核心实现在 SmartRefreshLayout.java它持有 Header/Footer 和 RefreshContent统一调度状态切换。RefreshHeader下拉头部的事件处理和显示接口定义见 RefreshHeader.java老版本包名为com.scwang.smartrefresh.layout.api的接口只是对内核接口的透传见 RefreshHeader.java。RefreshFooter上拉底部的事件处理和显示接口在 RefreshFooter.java 中比 Header 多了一个setNoMoreData(boolean)方法用于加载完成、没有更多数据的终态显示。RefreshContent对不同内容的统一封装包括判断是否可滚动、回弹判断、智能识别。定义见 RefreshContent.java其中canRefresh()/canLoadMore()决定当前内容是否允许触发刷新/加载moveSpinner()负责把 Header/Footer 位移同步到内容视图上。实际上RefreshHeader与RefreshFooter的公共部分被抽象成了RefreshComponent接口见 RefreshComponent.javaHeader/Footer 都只是它的语义化扩展。下面是 UML 关系类图相比其他下拉控件的优势特点网上其他的开源下拉控件同样可以自定义 Header 和 FooterSmartRefreshLayout 的优势主要体现在变换方式和独立事件两个方面。变换方式五种 SpinnerStyle框架为 Header/Footer 内置了五种视图变换方式枚举定义在 SpinnerStyle.java变换方式源码常量特点Translate 平行移动SpinnerStyle.Translate最常见HeaderView 高度不会改变随内容一起平移Scale 拉伸形变SpinnerStyle.Scale下拉和上弹时 HeaderView 高度改变会自动触发 OnDraw 事件FixedFront 固定在前面SpinnerStyle.FixedFront不会上下移动HeaderView 高度不会改变浮在内容上层FixedBehind 固定在后面SpinnerStyle.FixedBehind不会上下移动HeaderView 高度不会改变类似微信浏览器效果MatchLayout 全屏幕SpinnerStyle.MatchLayout固定在前面尺寸充满整个 RefreshLayout即旧文档所称的 Screen 全屏幕从源码看SpinnerStyle的每个实例都带有front是否浮于内容前方和scale是否需要动态测量/布局两个布尔属性Scale在拖动过程中会动态测量 Header 并重新布局会降低 App 性能因此源码中已将其标记为Deprecated官方自带 Header 都已从 Scale 转向 FixedBehind 来提高性能——自定义 Header 时也建议优先考虑 FixedBehind 方案可参考官方的飞机、贝塞尔、快递等 Header。其余四种高度不变仅通过front字段决定绘制层级。不同变换方式的 Header 实际效果演示FixedBehind 固定在后面和Scale 拉伸形变MatchLayout 全屏幕和Translate 平行移动独立事件Header 可以自己消费触摸事件Header 和 Footer 可以独立地处理手指滑动事件来为动画提供操作指令也可以使用 RefreshLayout 的核心接口来完成一些不寻常的操作指令。下面的打砖块 Header 中Header 可以独立地使用滑动事件来为游戏挡板提供指令并同时可以调用核心接口来通知 RefreshLayout 上下滚动列表这一能力的底层支撑是RefreshComponent接口中的isSupportHorizontalDrag()与onHorizontalDrag(percentX, offsetX, offsetMax)前者声明是否支持水平方向的拖动源码注释明确说明水平拖动需要消耗更多的时间和资源所以如果不支持请返回 false后者在支持时被回调把手指的水平坐标占比和偏移量交给 Header 做游戏控制之类的自定义逻辑。与此同时Header 还可以通过onInitialized拿到的RefreshKernel核心接口见 RefreshKernel.java主动驱动框架例如moveSpinner(int spinner, boolean isDragging)移动视图到指定位置、animSpinner(int endSpinner)执行回弹动画、startTwoLevel(boolean)开启二级刷新等。RefreshHeader 接口详解实现自定义 Header 的第一步就是实现RefreshHeader接口。结合 RefreshComponent.java 中的完整定义接口方法及其语义如下public interface RefreshHeader extends RefreshComponent { // RefreshComponent 中定义的核心方法 /** * 获取真实视图必须返回不能为null */ NonNull View getView(); /** * 获取变换方式必须指定一个平移、拉伸、固定、全屏 */ NonNull SpinnerStyle getSpinnerStyle(); /** * 【仅限框架内调用】设置主题颜色如果自定义的Header没有注意颜色本方法可以什么都不处理 * param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor */ void setPrimaryColors(ColorInt int ... colors); /** * 【仅限框架内调用】尺寸定义初始化完成 * 如果高度不改变代码修改setHeader只调用一次在 RefreshLayout#onMeasure 中调用 * param kernel RefreshKernel 核心接口用于完成高级Header功能 * param height HeaderHeight or FooterHeight * param maxDragHeight 最大拖动高度 */ void onInitialized(NonNull RefreshKernel kernel, int height, int maxDragHeight); /** * 【仅限框架内调用】手指拖动/回弹动画过程中的移动回调会连续多次调用用于实时控制动画关键帧 * param isDragging true 手指正在拖动 false 回弹动画 * param percent 下拉的百分比 值 offset/headerHeight0 - percent - (headerHeightmaxDragHeight)/headerHeight * param offset 下拉的像素偏移量0 - offset - (headerHeightmaxDragHeight) * param height 高度 HeaderHeight or FooterHeightoffset 可以超过 height 此时 percent 大于 1 * param maxDragHeight 最大拖动高度 */ void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight); /** * 【仅限框架内调用】释放时刻调用一次将会触发刷新/加载 * param refreshLayout RefreshLayout * param height 高度 HeaderHeight or FooterHeight * param maxDragHeight 最大拖动高度 */ void onReleased(NonNull RefreshLayout refreshLayout, int height, int maxDragHeight); /** * 【仅限框架内调用】开始动画开始刷新或者开始加载动画 */ void onStartAnimator(NonNull RefreshLayout refreshLayout, int height, int maxDragHeight); /** * 【仅限框架内调用】动画结束 * param success 数据是否成功刷新或加载 * return 完成动画所需时间如果返回 Integer.MAX_VALUE 将取消本次完成事件继续保持原有状态 */ int onFinish(NonNull RefreshLayout refreshLayout, boolean success); /** * 是否支持水平方向的拖动将会影响到 onHorizontalDrag 的调用 */ boolean isSupportHorizontalDrag(); /** * 水平方向的拖动配合 isSupportHorizontalDrag 使用 * param percentX 下拉时手指水平坐标对屏幕的占比0 - percentX - 1 * param offsetX 下拉时手指水平坐标对屏幕的偏移0 - offsetX - LayoutWidth * param offsetMax 最大的偏移量 */ void onHorizontalDrag(float percentX, int offsetX, int offsetMax); }说明老版本文档如 art/md_custom.md中的onPulling/onReleasing两个回调在 RefreshComponent.java 中已被合并为带isDragging参数的onMoving方法源码注释添加 isDragging 并取代之前的 onPulling、onReleasingonStateChanged则继承自OnStateChangedListener负责状态机切换通知。自定义实现时以当前内核接口为准。onStateChanged回调会携带新旧两个RefreshState状态。完整的刷新状态机定义在 RefreshState.java其中与 Header 相关的主要状态包括None无状态初始状态PullDownToRefresh手指下拉中isDragging trueReleaseToRefresh下拉超过触发线释放立即刷新RefreshReleased手指已释放即将进入刷新Refreshing正在刷新isOpening trueRefreshFinish刷新完成isFinishing true。每个状态都带有一组布尔标志isHeader、isDragging、isOpening、isFinishing、isTwoLevel、isReleaseToOpening框架内部通过toHeader()/toFooter()在 Header 与 Footer 状态之间互转因此同一套状态机可以同时驱动上下两端。手写一个经典 Header从需求分析到完整实现接下来通过实现一个最简单的经典 Header没有更新时间来完整走一遍自定义 Header 的流程。仓库中功能完备的官方版本见 ClassicsHeader.java这里我们按原文档的思路从零构建教学版。需求分析与成员变量经典 Header 需要三个元素标题文本、下拉箭头、刷新动画。由此选定继承LinearLayout并声明如下成员变量public class ClassicsHeader extends LinearLayout implements RefreshHeader { private TextView mHeaderText;//标题文本 private PathsView mArrowView;//下拉箭头 private ImageView mProgressView;//刷新动画视图 private ProgressDrawable mProgressDrawable;//刷新动画 public ClassicsHeader(Context context) { super(context); setGravity(Gravity.CENTER_HORIZONTAL); mHeaderText new TextView(context); mProgressDrawable new ProgressDrawable(); mArrowView new PathsView(context); mProgressView new ImageView(context); mProgressView.setImageDrawable(mProgressDrawable); addView(mProgressView); addView(mArrowView); addView(mHeaderText); } }其中的ProgressDrawable旋转加载动画与PathsView矢量路径箭头在仓库中均有现成实现ProgressDrawable位于 refresh-drawable-paint/src/main/java/com/scwang/smart/drawable/ProgressDrawable.javaPathsView位于 refresh-drawable-path/src/main/java/com/scwang/smart/drawable/path/view/PathsView.java可直接复用。指定样式变换方式根据我们的常识经典 Header 在下拉的时候是贴着列表平移向下冒出所以样式直接指定为平移public class ClassicsHeader extends LinearLayout implements RefreshHeader { NonNull public View getView() { return this;//真实的视图就是自己不能返回null } Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate;//指定为平移不能null } }getView()与getSpinnerStyle()是接口中仅有的两个必填方法前者返回真实视图不能为 null后者必须从SpinnerStyle的五个常量中指定一个。如果想让 Header 固定浮在内容上方不动就返回SpinnerStyle.FixedFront想要微信浏览器式的吸顶效果则返回SpinnerStyle.FixedBehind官方经典 Header 在 XML 中可通过srlClassicsSpinnerStyle属性配置见 ClassicsHeader.java 中对R.styleable.ClassicsHeader_srlClassicsSpinnerStyle的解析。动画开关接下来需要在关键地方对动画进行控制和开启public class ClassicsHeader extends LinearLayout implements RefreshHeader { Override public void onStartAnimator(RefreshLayout layout, int headHeight, int maxDragHeight) { mProgressDrawable.start();//开始动画 } Override public int onFinish(RefreshLayout layout, boolean success) { mProgressDrawable.stop();//停止动画 if (success){ mHeaderText.setText(刷新完成); } else { mHeaderText.setText(刷新失败); } return 500;//延迟500毫秒之后再弹回 } }onStartAnimator在手指释放、进入刷新状态时被调用是启动加载动画的最佳时机onFinish在数据回调finishRefresh()后触发返回值表示完成动画所需时间毫秒框架会等待该时长后再执行回弹——返回Integer.MAX_VALUE则可取消本次完成事件。官方经典 Header 在这里额外维护了上次更新时间并持久化到SharedPreferences见 ClassicsHeader.java 的setLastUpdateTime与KEY_LAST_UPDATE_TIME这是自定义时很实用的进阶点。状态控制还要在不同的状态控制内部控件的显示和旋转public class ClassicsHeader extends LinearLayout implements RefreshHeader { Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: case PullDownToRefresh: mHeaderText.setText(下拉开始刷新); mArrowView.setVisibility(VISIBLE);//显示下拉箭头 mProgressView.setVisibility(GONE);//隐藏动画 mArrowView.animate().rotation(0);//还原箭头方向 break; case Refreshing: mHeaderText.setText(正在刷新); mProgressView.setVisibility(VISIBLE);//显示加载动画 mArrowView.setVisibility(GONE);//隐藏箭头 break; case ReleaseToRefresh: mHeaderText.setText(释放立即刷新); mArrowView.animate().rotation(180);//显示箭头改为朝上 break; } } }这是自定义 Header 的核心人机交互逻辑PullDownToRefresh时展示向下的箭头提示用户继续下拉ReleaseToRefresh时把箭头旋转 180° 提示释放立即刷新Refreshing时切换为加载动画。官方实现ClassicsHeader.java还在ReleaseToTwoLevel状态显示释放进入二楼文字、在Loading状态复用同一套控件做加载文案切换印证了状态机驱动的设计思路。最后整合虽然接口的其他方法我们不用特意去实现但方法体还是要声明一下。整合之后完整代码如下public class ClassicsHeader extends LinearLayout implements RefreshHeader { private TextView mHeaderText;//标题文本 private PathsView mArrowView;//下拉箭头 private ImageView mProgressView;//刷新动画视图 private ProgressDrawable mProgressDrawable;//刷新动画 public ClassicsHeader(Context context) { super(context); initView(context); } public ClassicsHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context); } public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context); } private void initView(Context context) { setGravity(Gravity.CENTER); mHeaderText new TextView(context); mProgressDrawable new ProgressDrawable(); mArrowView new PathsView(context); mProgressView new ImageView(context); mProgressView.setImageDrawable(mProgressDrawable); mArrowView.parserPaths(M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z); addView(mProgressView, SmartUtil.dp2px(20), SmartUtil.dp2px(20)); addView(mArrowView, SmartUtil.dp2px(20), SmartUtil.dp2px(20)); addView(new View(context), SmartUtil.dp2px(20), SmartUtil.dp2px(20)); addView(mHeaderText, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); setMinimumHeight(SmartUtil.dp2px(60)); } NonNull public View getView() { return this;//真实的视图就是自己不能返回null } Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate;//指定为平移不能null } Override public void onStartAnimator(RefreshLayout layout, int headHeight, int maxDragHeight) { mProgressDrawable.start();//开始动画 } Override public int onFinish(RefreshLayout layout, boolean success) { mProgressDrawable.stop();//停止动画 if (success){ mHeaderText.setText(刷新完成); } else { mHeaderText.setText(刷新失败); } return 500;//延迟500毫秒之后再弹回 } Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: case PullDownToRefresh: mHeaderText.setText(下拉开始刷新); mArrowView.setVisibility(VISIBLE);//显示下拉箭头 mProgressView.setVisibility(GONE);//隐藏动画 mArrowView.animate().rotation(0);//还原箭头方向 break; case Refreshing: mHeaderText.setText(正在刷新); mProgressView.setVisibility(VISIBLE);//显示加载动画 mArrowView.setVisibility(GONE);//隐藏箭头 break; case ReleaseToRefresh: mHeaderText.setText(释放立即刷新); mArrowView.animate().rotation(180);//显示箭头改为朝上 break; } } Override public boolean isSupportHorizontalDrag() { return false; } Override public void onInitialized(RefreshKernel kernel, int height, int maxDragHeight) { } Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } Override public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { } Override public void onReleased(RefreshLayout layout, int height, int maxDragHeight) { } Override public void setPrimaryColors(ColorInt int ... colors){ } }把这段代码放入布局 XML 中并指定给SmartRefreshLayout的app:srlHeader属性即可获得一个可用的自定义刷新头部。官方的 ClassicsHeader.java 在完整版中还会通过obtainStyledAttributes解析srlPrimaryColor、srlAccentColor、srlDrawableArrowSize、srlDrawableProgressSize、srlTextSizeTitle、srlFinishDuration、srlEnableLastTime等自定义属性并在onInitialized中把RefreshKernel保存下来供setEnableLastTime/setTextSizeTime等链式 API 调用requestRemeasureHeightFor(this)动态重测高度——这些都是让你的自定义组件工业化的参考范本。实现 RefreshFooterRefreshFooter的具体实现方法和RefreshHeader非常相似同样继承RefreshComponent需要实现getView()、getSpinnerStyle()、onStateChanged()、onStartAnimator()、onFinish()等一套方法唯一的差别是状态机换成了 Footer 一侧的PullUpToLoad、ReleaseToLoad、Loading、LoadFinish通过 RefreshState.java 的toFooter()自动映射。此外RefreshFooter接口见 RefreshFooter.java还额外要求实现一个方法/** * 【仅限框架内调用】设置数据全部加载完成将不能再次触发加载功能 * param noMoreData 是否有更多数据 * return true 支持全部加载完成的状态显示 false 不支持 */ boolean setNoMoreData(boolean noMoreData);当业务数据全部加载完毕时框架会调用setNoMoreData(true)此时 Footer 应切换为没有更多数据的终态文案并禁止再次触发加载。仓库中的官方实现可参考 refresh-footer-classics/src/main/java/com/scwang/smart/refresh/footer/ClassicsFooter.java 与 refresh-footer-ball/src/main/java/com/scwang/smart/refresh/footer/BallPulseFooter.java。结语SmartRefreshLayout 的自定义能力可以总结为一句话框架负责拉动与回弹你只负责样子与动画。通过实现RefreshHeader/RefreshFooter接口、选择适合的SpinnerStyle变换方式、响应RefreshState状态机回调再配合RefreshKernel核心接口实现二级刷新、请求触摸事件等高级玩法你就能把美团、饿了么、天猫式的品牌吉祥物以及任何你想象得到的创意动画稳定地植入到下拉刷新与上拉加载的交互中。仓库内 refresh-header 与 refresh-footer 目录下的全部官方实现都是进一步学习的最佳源码教材。赞分享移动开发UI组件【免费下载链接】SmartRefreshLayout下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScrollAndroid智能下拉刷新框架支持越界回弹、越界拖动具有极强的扩展性集成了几十种炫酷的Header和 Footer。项目地址https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout点击查看免费下载相关推荐SmartRefreshLayout自定义Header/Footer完全指南SmartRefreshLayout自定义Header/Footer完全指南 引言告别千篇一律的刷新体验 你是否还在为App中单调的下拉刷新样式感到困扰是否移动开发UI组件SmartRefreshLayout自定义刷新状态管理从原理到实战SmartRefreshLayout自定义刷新状态管理从原理到实战 一、刷新状态管理痛点与解决方案 你是否还在为下拉刷新状态混乱而困扰SmartRefres移动开发UI组件《仓颉语言实战》仓颉接口完全指南从定义、实现到接口继承与内置接口《仓颉语言实战》仓颉接口完全指南从定义、实现到接口继承与内置接口 《仓颉语言实战》是张磊编写、清华大学出版社出版的零基础入门仓颉语言书籍。本文作为一篇 仓颉接示例工程上一篇彻底解决docker-stacks中R包安装依赖系统库与外部依赖完整指南下一篇Sioyek性能测试工具开发者必备的优化利器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。