查看: 167|回复: 0

鸿蒙RefreshControl下拉刷新适配与排障实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
React Native 里做列表下拉刷新,RefreshControl 是常用组件。它本身 API 不复杂,但在鸿蒙上使用时,真正需要关注的是平台差异:鸿蒙继承 Android 这一套属性,和 iOS 的 tintColor、title 等并不通用;同时 colors、下拉阈值、嵌套滚动、refreshing 状态重置等细节,容易导致指示器不出现、颜色不生效或 loading 一直转。下面按实际适配与排障过程整理。

一、基本用法:refreshing 是受控状态
  1. import { useState, useCallback } from 'react';
  2. import { ScrollView, RefreshControl, Text } from 'react-native';
  3. function MyList() {
  4.   const [refreshing, setRefreshing] = useState(false);
  5.   const onRefresh = useCallback(async () => {
  6.     setRefreshing(true);
  7.     await fetchNewData();
  8.     setRefreshing(false);
  9.   }, []);
  10.   return (
  11.     <ScrollView
  12.       refreshControl={
  13.         <RefreshControl
  14.           refreshing={refreshing}
  15.           onRefresh={onRefresh}
  16.         />
  17.       }
  18.     >
  19.       <Text>列表内容...</Text>
  20.     </ScrollView>
  21.   );
  22. }
复制代码
关键只有两个属性:refreshing 控制是否显示 loading,onRefresh 是下拉触发回调。要注意 refreshing 是受控属性,必须在 onRefresh 中手动设为 true,请求完成后再设为 false。如果忘了设为 false,loading 会一直转。这个问题在鸿蒙真机上同样常见,调试时先检查状态是否复位。

二、FlatList 的两种接入方式
FlatList 也可以用 RefreshControl,而且有两种写法:
  1. // 方式一:通过 refreshControl 属性
  2. <FlatList
  3.   data={items}
  4.   renderItem={renderItem}
  5.   refreshControl={
  6.     <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
  7.   }
  8. />
  9. // 方式二:通过专门的 refreshing 和 onRefresh 属性
  10. <FlatList
  11.   data={items}
  12.   renderItem={renderItem}
  13.   refreshing={refreshing}
  14.   onRefresh={onRefresh}
  15. />
复制代码
两种方式效果一样,选哪种看个人习惯。方式一可以顺便设置颜色等 RefreshControl 属性,在鸿蒙适配时更灵活。

三、颜色属性的平台差异
默认指示器是灰色,某些 App 里不够醒目。RefreshControl 支持自定义颜色,但平台差异明显:
  1. <RefreshControl
  2.   refreshing={refreshing}
  3.   onRefresh={onRefresh}
  4.   colors={['#8B5CF6', '#EC4899']}
  5.   tintColor='#8B5CF6'
  6.   title='正在刷新...'
  7.   titleColor='#8B5CF6'
  8. />
复制代码
colors 只在 Android 和鸿蒙上生效,而且必须传数组,可以多个颜色轮播。传字符串不会生效。tintColor、title、titleColor 是 iOS 专用。多平台兼容时,两边都要写。鸿蒙侧主要关注 colors 和 progressViewOffset。

四、鸿蒙上的常见踩坑
1. colors 不生效
写了 colors={['#8B5CF6']},指示器仍是灰色,可能是鸿蒙版本较低,该属性在某些版本上不支持。升级系统后恢复正常。因此颜色设置无效时,先检查鸿蒙版本。
2. 下拉距离不够
用户反馈“下拉了但没触发刷新”,通常是因为下拉距离未超过 RefreshControl 阈值。这个阈值由系统控制,无法通过属性调整,只能通过视觉提示告诉用户“再拉一点”。
3. ScrollView 嵌套冲突
如果 ScrollView 内又嵌套 ScrollView,RefreshControl 可能失效。外层 ScrollView 先处理了下拉手势,内层收不到。解决方式是避免嵌套,或只在最外层使用 RefreshControl。
4. refreshing 状态卡住
网络请求失败时,如果忘了在 catch 里把 refreshing 设为 false,loading 会一直转。建议用 try-finally:
  1. const onRefresh = useCallback(async () => {
  2.   setRefreshing(true);
  3.   try {
  4.     await fetchData();
  5.   } catch (error) {
  6.     console.error('刷新失败:', error);
  7.   } finally {
  8.     setRefreshing(false);
  9.   }
  10. }, []);
复制代码
5. 固定头部遮挡指示器
页面有固定头部或导航栏时,下拉指示器可能被挡住。可以用 progressViewOffset 让指示器向下偏移:
  1. <RefreshControl
  2.   refreshing={refreshing}
  3.   onRefresh={onRefresh}
  4.   progressViewOffset={100}
  5. />
复制代码
这个属性只在 Android 和鸿蒙上生效,iOS 只能接受系统默认位置。

五、封装通用 RefreshableList
真实项目中,通常会把刷新状态、颜色、空列表、头尾组件封装起来:
  1. import React, { useState, useCallback, ReactNode } from 'react';
  2. import { FlatList, RefreshControl, View } from 'react-native';
  3. type Props<T> = {
  4.   data: T[];
  5.   renderItem: (item: T, index: number) => ReactNode;
  6.   onRefresh?: () => Promise<void>;
  7.   keyExtractor?: (item: T, index: number) => string;
  8.   ListEmptyComponent?: ReactNode;
  9.   ListHeaderComponent?: ReactNode;
  10.   ListFooterComponent?: ReactNode;
  11. };
  12. export function RefreshableList<T>({
  13.   data,
  14.   renderItem,
  15.   onRefresh,
  16.   keyExtractor,
  17.   ListEmptyComponent,
  18.   ListHeaderComponent,
  19.   ListFooterComponent,
  20. }: Props<T>) {
  21.   const [refreshing, setRefreshing] = useState(false);
  22.   const handleRefresh = useCallback(async () => {
  23.     if (!onRefresh) return;
  24.     setRefreshing(true);
  25.     try {
  26.       await onRefresh();
  27.     } finally {
  28.       setRefreshing(false);
  29.     }
  30.   }, [onRefresh]);
  31.   return (
  32.     <FlatList
  33.       data={data}
  34.       keyExtractor={keyExtractor ?? ((_, i) => String(i))}
  35.       renderItem={({ item, index }) => renderItem(item, index)}
  36.       refreshControl={
  37.         onRefresh ? (
  38.           <RefreshControl
  39.             refreshing={refreshing}
  40.             onRefresh={handleRefresh}
  41.             colors={['#8B5CF6']}
  42.             tintColor='#8B5CF6'
  43.           />
  44.         ) : undefined
  45.       }
  46.       ListEmptyComponent={ListEmptyComponent ? <View>{ListEmptyComponent}</View> : null}
  47.       ListHeaderComponent={ListHeaderComponent ? <View>{ListHeaderComponent}</View> : null}
  48.       ListFooterComponent={ListFooterComponent ? <View>{ListFooterComponent}</View> : null}
  49.     />
  50.   );
  51. }
复制代码
封装后使用更简单:
  1. <RefreshableList
  2.   data={items}
  3.   renderItem={(item) => <ItemCard item={item} />}
  4.   onRefresh={async () => {
  5.     const newItems = await fetchItems();
  6.     setItems(newItems);
  7.   }}
  8.   ListEmptyComponent={<EmptyState text='暂无数据' />}
  9. />
复制代码

六、下拉刷新与上拉加载的区别
下拉刷新是拉到顶部继续下拉,触发刷新并获取最新数据;上拉加载是拉到底部继续上拉,触发加载更多并获取下一页。RefreshControl 只负责下拉刷新,上拉加载通常用 FlatList 的 onEndReached:
  1. <FlatList
  2.   data={items}
  3.   onEndReached={loadMore}
  4.   onEndReachedThreshold={0.5}
  5.   refreshControl={
  6.     <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
  7.   }
  8. />
复制代码

七、商品列表刷新逻辑
电商商品列表是典型场景。刷新时应重新请求第一页,再替换旧数据;加载更多才追加下一页,否则刷新后数据会重复:
[code]
import { useState, useCallback, useEffect } from 'react';
import { FlatList, RefreshControl } from 'react-native';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [refreshing, setRefreshing] = useState(false);
  const [page, setPage] = useState(1);

  useEffect(() => {
    loadProducts(1);
  }, []);

  const loadProducts = async (pageNum: number) => {
    const res = await fetch('/api/products?page=' + pageNum);
    const data = await res.json();
    if (pageNum === 1) {
      setProducts(data.list);
    } else {
      setProducts((prev) => [...prev, ...data.list]);
    }
    setPage(pageNum);
  };

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    try {
      await loadProducts(1);
    } finally {
      setRefreshing(false);
    }
  }, []);

  const onEndReached = useCallback(async () => {
    await loadProducts(page + 1);
  },
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-17 10:12 , Processed in 0.020456 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部