Android 下拉刷新:SwipeRefreshLayout 原理、用法与 API 详解
2026/9/4 21:17:59 网站建设 项目流程

文章目录

    • 一、SwipeRefreshLayout 的定位
    • 二、布局使用详解
      • 2.1 布局骨架
      • 2.2 必须遵守的两条布局规则
    • 三、代码接入:基本三步
    • 四、设置方法详解(API)
      • 4.1 状态控制
      • 4.2 监听器设置
      • 4.3 外观定制
      • 4.4 转圈位置与触发距离
      • 4.5 嵌套滚动
    • 五、进阶细节:仅在滚动到顶部时允许刷新
    • 六、常见问题
    • 七、小结

下拉刷新(Pull-to-Refresh)是移动端最常见的交互之一:用户在可滚动区域的顶部继续下拉,松手后触发数据重新加载,顶部出现转圈动画作为加载反馈。Android 官方实现为SwipeRefreshLayoutandroidx.swiperefreshlayout.widget),本文结合天气 App 的实际用法,从布局结构到全部常用设置方法逐一详解。


一、SwipeRefreshLayout 的定位

SwipeRefreshLayout继承自ViewGroup,是官方提供的一个通用容器控件,自身不绘制业务内容,职责只有两件:

  1. 监听手势:当子 View 已滚动到顶部、用户继续向下拖动时,判定为一次"刷新意图";
  2. 状态反馈:绘制转圈动画,手势结束时回调刷新监听器,由监听器执行真正的数据加载。

因此布局上它必须作为最外层容器存在,把需要支持刷新的内容整体包裹起来。


二、布局使用详解

2.1 布局骨架

以我的天气 App 为例,页面结构是:

SwipeRefreshLayout // 最外层:下拉刷新的载体 └─ ConstraintLayout // 页面内容容器 ├─ 顶部标题栏(城市名、搜索按钮) └─ ScrollView // 可滚动正文 └─ 温度、湿度、逐小时预报等内容<androidx.swiperefreshlayout.widget.SwipeRefreshLayoutandroid:id="@+id/swipe_refresh"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/tv_city_name"android:layout_width="wrap_content"android:layout_height="wrap_content"/><ScrollViewandroid:id="@+id/scroll_view"android:layout_width="0dp"android:layout_height="0dp"android:fillViewport="true"android:overScrollMode="never"android:scrollbars="none"><!-- 天气正文内容 --></ScrollView></androidx.constraintlayout.widget.ConstraintLayout></androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

2.2 必须遵守的两条布局规则

规则一:SwipeRefreshLayout 只能有一个直接子 View。

它需要明确"该监听哪个 View 的滚动"。若内容由多个控件并列组成,必须先包一层容器(LinearLayout/ConstraintLayout/ScrollView/RecyclerView)再作为唯一直接子 View;直接放置两个及以上子 View,运行期会抛出:

IllegalArgumentException: SwipeRefreshLayout can host only one direct child

规则二:子内容必须是可滚动的。

下拉刷新手势的前提是"子 View 已滚到顶部(无法继续上滚)后仍向下拉"。若内容短于一屏、本身不能滚动,手势不成立,下拉不会有反应。所以内部通常使用ScrollViewRecyclerView


三、代码接入:基本三步

publicclassMainActivityextendsAppCompatActivity{privateSwipeRefreshLayoutswipeRefresh;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);swipeRefresh=findViewById(R.id.swipe_refresh);// ① 注册刷新监听:下拉松手后触发数据重新加载swipeRefresh.setOnRefreshListener(this::startWeather);}// ② 刷新方法:亮出转圈 → 发起请求privatevoidstartWeather(){swipeRefresh.setRefreshing(true);// 显示转圈// ... 发起网络请求(OkHttp 等)...}// ③ 请求结束(无论成功失败)必须收圈privatevoidonDataLoaded(){swipeRefresh.setRefreshing(false);// 隐藏转圈// ... 更新 UI ...}}

最容易犯的错误:忘记第 ③ 步。如果请求失败路径没有调setRefreshing(false),转圈将永久显示,用户会认为应用卡死。正确写法应把收圈逻辑放在成功与失败两个回调里都执行。

忘记关闭的效果如下:

正确效果如下:


四、设置方法详解(API)

4.1 状态控制

方法说明
setRefreshing(boolean refreshing)程序化控制转圈显隐。传true立即显示转圈且不播放进入动画(常用于代码主动刷新);传false隐藏
isRefreshing()返回当前是否处于刷新状态
setEnabled(boolean enabled)是否允许下拉刷新手势。常用于"仅滚动到顶部时允许刷新"
isEnabled()当前是否允许下拉手势

示例:进入页面时自动刷新一次(无需用户手势):

swipeRefresh.setRefreshing(true);startWeather();

4.2 监听器设置

setOnRefreshListener(OnRefreshListener listener)

刷新监听,内部只定义了一个方法onRefresh(),在下拉松手(或程序触发)时回调:

swipeRefresh.setOnRefreshListener(()->{// 在这里重新加载数据fetchWeather();});

注意onRefresh()回调发生时,转圈已经由控件自动显示,不需要也不应在回调里手动调setRefreshing(true);回调只负责发起加载。

setOnRefreshListener(OnChildScrollUpCallback callback, OnRefreshListener listener)

带"是否可以上滚回调"的版本,OnChildScrollUpCallback用于自定义"何时判定为已到顶部",适用于内置判断不满足需求的场景:

swipeRefresh.setOnRefreshListener(newSwipeRefreshLayout.OnChildScrollUpCallback(){@OverridepublicbooleancanChildScrollUp(SwipeRefreshLayoutparent,Viewchild){// 返回 true 表示"子 View 还可以继续上滚",此时不触发刷新returnchild.canScrollVertically(-1);}},()->fetchWeather());

setOnChildScrollUpCallback(OnChildScrollUpCallback callback)

仅设置上滚判定回调(不重复注册刷新监听),可动态更换判定逻辑:

swipeRefresh.setOnChildScrollUpCallback((parent,child)->child.getScrollY()>0);// 例如:ScrollView 滚动过就禁止刷新

4.3 外观定制

① 转圈颜色 ——setColorSchemeColors(int... colors)

设置转圈动画的颜色序列,转圈会按传入颜色依次渐变:

swipeRefresh.setColorSchemeColors(ContextCompat.getColor(this,R.color.colorPrimary),ContextCompat.getColor(this,R.color.colorAccent),0xFFFF3333);

② 转圈颜色 ——setColorSchemeResources(int... colorResIds)(等价资源写法)

swipeRefresh.setColorSchemeResources(R.color.blue,R.color.green,R.color.orange);

③ 转圈背景圆盘颜色

转圈默认显示在白色圆盘上;若界面背景为深色,可自定义圆盘颜色:

swipeRefresh.setProgressBackgroundColorSchemeColor(ContextCompat.getColor(this,R.color.white));// 或资源写法:swipeRefresh.setProgressBackgroundColorSchemeResource(R.color.white);

④ 转圈尺寸 ——setSize(int size)

SwipeRefreshLayout.SIZE_DEFAULT(默认)与SIZE_LARGE(大号)二选一:

swipeRefresh.setSize(SwipeRefreshLayout.SIZE_LARGE);

改动效果如下:

4.4 转圈位置与触发距离

下拉时转圈会跟随手指下移,可通过偏移方法调整它的初始位置与跟随范围。

① 设置转圈起始偏移 ——setProgressViewOffset(boolean scale, int start, int end)

  • scale:是否以缩放动画出现;
  • start:转圈在父容器中的起始偏移(通常设为触发下拉前的停留位置,一般取一个屏幕高度如screenHeightPx,让它在手指下拉时才出现);
  • end:转圈完全出现后的结束偏移。
intscreenHeightPx=getResources().getDisplayMetrics().heightPixels;swipeRefresh.setProgressViewOffset(false,screenHeightPx/3,screenHeightPx/4);

② 设置转圈距离父容器顶部的初始距离 ——setProgressViewEndTarget(boolean scale, int end)

只指定转圈出现后的最终位置:

swipeRefresh.setProgressViewEndTarget(false,dp2px(96));

③ 下拉触发刷新的最小距离 ——setDistanceToTriggerSync(int distance)

手指下拉超过该像素距离(松手)才触发刷新。默认值由系统计算(大致为一个屏幕高度),数值越小越"灵敏":

swipeRefresh.setDistanceToTriggerSync(dp2px(120));

④ 松手回弹的最大下拉距离 ——setSlingshotDistance(int distance)

限制转圈在手指持续下拉时能跟随的最大位移(AndroidX 1.1.0+):

swipeRefresh.setSlingshotDistance(dp2px(80));

辅助换算方法:

privateintdp2px(floatdp){return(int)(dp*getResources().getDisplayMetrics().density+0.5f);}

4.5 嵌套滚动

SwipeRefreshLayout默认开启了嵌套滚动支持,可被外层NestedScrollView/CoordinatorLayout正确协调。只有内容自身实现了复杂拖动逻辑(如自绘RecyclerView拖拽排序)时才可能需要关闭:

swipeRefresh.setNestedScrollingEnabled(false);

五、进阶细节:仅在滚动到顶部时允许刷新

页面内容较长时,用户滚到中间继续下拉,预期是"继续浏览"而非"刷新"。若不加限制会打断浏览体验,因此应只在内容处于顶部时开启刷新

ScrollView,监听滚动位置:

scrollView.setOnScrollChangeListener((v,scrollX,scrollY,oldScrollX,oldScrollY)->swipeRefresh.setEnabled(scrollY==0));

scrollY == 0表示尚未向下滚动,此时允许下拉刷新;一旦滚离顶部立即禁用刷新,把下拉手势交还给滚动。

RecyclerView,利用 LayoutManager 判断首项是否完全可见:

recyclerView.addOnScrollListener(newRecyclerView.OnScrollListener(){@OverridepublicvoidonScrolled(@NonNullRecyclerViewrv,intdx,intdy){LinearLayoutManagerlm=(LinearLayoutManager)rv.getLayoutManager();booleanatTop=lm!=null&&lm.findFirstCompletelyVisibleItemPosition()==0;swipeRefresh.setEnabled(atTop);}});

如果没有设置刷新条件,会出现不能向上滑,而只能刷新的情况:


六、常见问题

问题原因解决
运行期崩溃:can host only one direct child直接子 View 多于一个先包一层容器,只保留一个直接子 View
转圈一直不消失刷新结束后没调setRefreshing(false)成功与失败回调里都要收圈
内容滚到一半下拉也弹转圈未做顶部判断setEnabled(scrollY == 0)或设置OnChildScrollUpCallback
下拉没反应内部不是可滚动控件,手势不成立换成ScrollView/RecyclerView
转圈颜色与主题不搭未自定义setColorSchemeResources(...)
深色背景看不到转圈白色圆盘与背景融为一体setProgressBackgroundColorSchemeResource(...)改圆盘颜色
CoordinatorLayout冲突/手势异常嵌套滚动协调问题检查setNestedScrollingEnabled(true)是否被误关

七、小结

下拉刷新的接入路径清晰稳定:

布局:SwipeRefreshLayout(唯一子 View = 可滚动内容容器) ↓ 代码:setOnRefreshListener 注册刷新回调 ↓ 刷新中:转圈由控件自动显示(setRefreshing(true) 可程序化触发) ↓ 结束:setRefreshing(false) 必须覆盖成功与失败两条路径

控件负责手势监听、转圈动画与状态机,真正的业务复杂度(网络请求、解析、UI 更新)全部收在onRefresh回调里——这是 Android 官方把通用交互沉淀为标准控件的典型设计。在此基础上,善用setColorSchemeResourcessetDistanceToTriggerSyncOnChildScrollUpCallback等配置方法,即可做出与产品风格一致、交互手感合适的下拉刷新体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询