RN for OpenHarmony 实战 TodoList 项目:加载状态 Loading

案例开源地址:https://atomgit.com/lqjmac/rn_openharmony_todolist

等待是最难熬的

点了一个按钮,然后什么反应都没有。你会想:点到了吗?要不要再点一下?是不是卡住了?

这种不确定感让人焦虑。加载状态就是为了消除这种焦虑。它告诉用户"我收到你的请求了,正在处理,请稍等"。

我们的 TodoList 应用是本地数据,不需要网络请求,加载几乎是瞬间完成的。但我们还是实现了加载状态,一方面是为了演示,另一方面是为了让应用感觉更完整。下拉刷新时显示"刷新中...",让用户知道刷新正在进行。


刷新时的加载提示

我们在下拉刷新时显示一个文字提示:

tsx 复制代码
{refreshing && <View style={styles.loadingOverlay}><Text style={styles.loadingText}>刷新中...</Text></View>}

这是条件渲染。只有当 refreshingtrue 时,加载提示才会显示。

刷新状态的管理

tsx 复制代码
const [refreshing, setRefreshing] = useState(false);

const onRefresh = () => {
  setRefreshing(true);
  setTimeout(() => setRefreshing(false), 1000);
};

refreshing 是一个布尔状态。下拉触发 onRefresh,把 refreshing 设为 true,1 秒后设为 false

这 1 秒是模拟网络延迟。真实应用里,这里应该是异步请求,请求完成后再把 refreshing 设为 false


加载提示的样式

tsx 复制代码
loadingOverlay: {position: 'absolute', top: 100, left: 0, right: 0, alignItems: 'center'},
loadingText: {color: '#6c5ce7', fontSize: 14},

绝对定位

position: 'absolute' 让加载提示脱离文档流,不会影响其他元素的布局。

top: 100 把提示放在页面上方。为什么是 100?因为要避开导航栏和统计区域。这个值是试出来的,你可以根据实际布局调整。

left: 0, right: 0 让容器占满宽度。alignItems: 'center' 让文字水平居中。

文字样式

color: '#6c5ce7' 是主题强调色,紫色。用强调色让加载提示更醒目。

fontSize: 14 是正常的字体大小,不大不小。


RefreshControl 的加载指示器

除了我们自定义的文字提示,RefreshControl 本身也有加载指示器:

tsx 复制代码
<FlatList 
  ...
  refreshControl={
    <RefreshControl 
      refreshing={refreshing} 
      onRefresh={onRefresh} 
      tintColor={theme.accent} 
    />
  }
/>

下拉时会出现一个旋转的圆圈,这是系统自带的。tintColor 设置圆圈的颜色,我们用主题强调色。

iOS 和 Android 的差异

iOS 上,tintColor 设置加载指示器的颜色。

Android 上,要用 colors 属性,是一个颜色数组:

tsx 复制代码
<RefreshControl 
  refreshing={refreshing} 
  onRefresh={onRefresh} 
  tintColor={theme.accent}
  colors={[theme.accent]}
/>

colors 数组可以有多个颜色,Android 会在刷新过程中循环使用这些颜色。我们只用一个颜色,保持简单。


两个加载指示器?

你可能注意到了,我们有两个加载指示器:RefreshControl 自带的旋转圆圈,和我们自定义的"刷新中..."文字。

这是故意的吗?其实是为了演示两种方式。真实应用里,选一种就够了。

RefreshControl 的圆圈是标准的下拉刷新体验,用户很熟悉。自定义文字提示可以显示更多信息,比如"正在同步数据..."、"加载第 2 页..."。

我们的应用两个都有,有点冗余。如果要精简,可以去掉自定义的文字提示,只保留 RefreshControl 的圆圈。


ActivityIndicator 组件

React Native 提供了 ActivityIndicator 组件,是一个标准的加载圆圈:

tsx 复制代码
import {ActivityIndicator} from 'react-native';

<ActivityIndicator size="large" color={theme.accent} />

size 可以是 'small''large',也可以是具体的数字(仅 Android)。

color 设置圆圈的颜色。

我们的应用没有用 ActivityIndicator,用的是文字提示。但如果你想要一个旋转的圆圈,ActivityIndicator 是最简单的选择。

带文字的加载组件

可以把 ActivityIndicator 和文字组合起来:

tsx 复制代码
const LoadingSpinner = () => (
  <View style={{alignItems: 'center', padding: 20}}>
    <ActivityIndicator size="large" color={theme.accent} />
    <Text style={{color: theme.subText, marginTop: 12}}>加载中...</Text>
  </View>
);

圆圈在上,文字在下,是很常见的加载状态设计。


全屏加载遮罩

有时候需要阻止用户操作,直到加载完成。可以用全屏遮罩:

tsx 复制代码
{isLoading && (
  <View style={{
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  }}>
    <ActivityIndicator size="large" color="#fff" />
    <Text style={{color: '#fff', marginTop: 12}}>请稍候...</Text>
  </View>
)}

半透明的黑色背景覆盖整个屏幕,用户看得到下面的内容但点不了。加载指示器在屏幕中央。

这种设计比较"重",会打断用户的操作。适合用在重要的操作上,比如提交订单、保存数据。普通的列表加载不建议用全屏遮罩。


骨架屏

骨架屏是另一种加载状态的展示方式。在数据加载完成前,显示内容的"骨架"------灰色的占位块,模拟最终内容的布局。

tsx 复制代码
const SkeletonCard = () => (
  <View style={{
    backgroundColor: theme.card,
    borderRadius: 12,
    padding: 16,
    marginBottom: 10,
  }}>
    <View style={{
      backgroundColor: theme.border,
      height: 20,
      width: '70%',
      borderRadius: 4,
      marginBottom: 12,
    }} />
    <View style={{
      backgroundColor: theme.border,
      height: 14,
      width: '40%',
      borderRadius: 4,
    }} />
  </View>
);

// 加载时显示骨架屏
{isLoading ? (
  <>
    <SkeletonCard />
    <SkeletonCard />
    <SkeletonCard />
  </>
) : (
  <FlatList data={tasks} ... />
)}

骨架屏的好处是让用户知道"内容马上就来",而且能看到大致的布局。比空白页面或者单纯的加载圆圈更友好。

骨架屏的动画

静态的骨架屏有点呆板。可以加一个闪烁动画,让它看起来"活"一点:

tsx 复制代码
const SkeletonCard = () => {
  const opacity = useRef(new Animated.Value(0.3)).current;

  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(opacity, {toValue: 1, duration: 500, useNativeDriver: true}),
        Animated.timing(opacity, {toValue: 0.3, duration: 500, useNativeDriver: true}),
      ])
    ).start();
  }, []);

  return (
    <Animated.View style={{opacity, ...}}>
      ...
    </Animated.View>
  );
};

透明度在 0.3 和 1 之间循环变化,形成闪烁效果。这种动画告诉用户"正在加载",比静止的灰块更有活力。


加载状态的时机

什么时候显示加载状态?

初始加载

应用启动时,数据还没加载完成。这时候可以显示骨架屏或者全屏加载。

我们的应用是本地数据,启动时数据就在那里,不需要初始加载状态。

下拉刷新

用户下拉刷新时,显示 RefreshControl 的加载指示器。这是我们实现的。

上拉加载更多

列表滚动到底部,加载下一页数据。可以在列表底部显示一个加载指示器。

tsx 复制代码
<FlatList 
  ...
  onEndReached={loadMore}
  onEndReachedThreshold={0.5}
  ListFooterComponent={isLoadingMore ? <ActivityIndicator /> : null}
/>

onEndReached 在滚动到底部时触发,ListFooterComponent 在列表底部显示加载指示器。

操作反馈

用户点击按钮执行操作,比如删除任务、提交表单。可以在按钮上显示加载状态,或者用全屏遮罩。


加载状态的持续时间

加载状态应该显示多久?

真实的加载时间

最理想的情况是显示真实的加载时间。数据加载完成,加载状态消失。

tsx 复制代码
const onRefresh = async () => {
  setRefreshing(true);
  try {
    await fetchData();
  } finally {
    setRefreshing(false);
  }
};

finally 确保不管成功还是失败,加载状态都会结束。

最短显示时间

有时候加载太快,加载状态一闪而过,用户可能没注意到。可以设置一个最短显示时间:

tsx 复制代码
const onRefresh = async () => {
  setRefreshing(true);
  const startTime = Date.now();
  
  try {
    await fetchData();
  } finally {
    const elapsed = Date.now() - startTime;
    const minDuration = 500;
    
    if (elapsed < minDuration) {
      await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
    }
    
    setRefreshing(false);
  }
};

如果加载时间不到 500 毫秒,等到 500 毫秒再结束加载状态。这样用户能看到加载过程,知道刷新确实执行了。

我们的应用用 setTimeout 固定 1 秒,是一种简化的做法。


加载失败的处理

加载不一定成功。网络错误、服务器错误、超时,都可能导致加载失败。

tsx 复制代码
const [error, setError] = useState<string | null>(null);

const onRefresh = async () => {
  setRefreshing(true);
  setError(null);
  
  try {
    await fetchData();
  } catch (e) {
    setError('加载失败,请重试');
  } finally {
    setRefreshing(false);
  }
};

// 显示错误提示
{error && (
  <View style={styles.errorContainer}>
    <Text style={styles.errorText}>{error}</Text>
    <TouchableOpacity onPress={onRefresh}>
      <Text style={styles.retryText}>点击重试</Text>
    </TouchableOpacity>
  </View>
)}

加载失败时显示错误信息和重试按钮。用户可以点击重试,不用手动下拉刷新。

我们的应用是本地数据,不会加载失败,所以没有实现错误处理。


按钮的加载状态

点击按钮执行操作时,可以在按钮上显示加载状态:

tsx 复制代码
const [isSubmitting, setIsSubmitting] = useState(false);

<TouchableOpacity 
  style={[styles.button, isSubmitting && styles.buttonDisabled]}
  onPress={handleSubmit}
  disabled={isSubmitting}
>
  {isSubmitting ? (
    <ActivityIndicator size="small" color="#fff" />
  ) : (
    <Text style={styles.buttonText}>提交</Text>
  )}
</TouchableOpacity>

提交时按钮显示加载圆圈,同时禁用按钮防止重复点击。提交完成后恢复正常。

这种设计让用户知道操作正在进行,同时不会误触发多次操作。


加载状态和用户体验

加载状态不只是技术实现,更是用户体验的一部分。

即时反馈

用户操作后,应该立即有反馈。哪怕数据还没加载完,也要先显示加载状态,让用户知道操作被接收了。

进度感知

如果加载时间较长,可以显示进度。比如"加载中 50%"、"正在处理第 3 步,共 5 步"。让用户知道还要等多久。

可取消

长时间的加载应该可以取消。用户可能改变主意,或者发现加载太慢想放弃。提供取消按钮是友好的设计。

后台加载

有些加载可以在后台进行,不阻塞用户操作。比如图片加载,可以先显示占位图,图片加载完成后再替换。


我们应用中的加载状态

回顾一下我们应用中的加载状态实现:

tsx 复制代码
// 状态
const [refreshing, setRefreshing] = useState(false);

// 刷新逻辑
const onRefresh = () => {
  setRefreshing(true);
  setTimeout(() => setRefreshing(false), 1000);
};

// RefreshControl
<FlatList 
  ...
  refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.accent} />}
/>

// 自定义提示
{refreshing && <View style={styles.loadingOverlay}><Text style={styles.loadingText}>刷新中...</Text></View>}

简单但完整。下拉触发刷新,显示加载状态,1 秒后结束。RefreshControl 提供标准的下拉刷新体验,自定义提示提供额外的文字反馈。


小结

加载状态告诉用户"正在处理,请稍等"。我们用 RefreshControl 实现下拉刷新的加载指示器,用条件渲染显示自定义的文字提示。ActivityIndicator 是标准的加载圆圈组件,骨架屏是更高级的加载状态展示方式。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
红目香薰13 小时前
GitCode-我的运气的可量化方案-更新v5版本
人工智能·开源·文心一言·gitcode
qq_4061761414 小时前
关于JavaScript中的filter方法
开发语言·前端·javascript·ajax·原型模式
@@小旭14 小时前
实现头部Sticky 粘性布局,并且点击菜单滑动到相应位置
前端·javascript·css
Van_captain15 小时前
rn_for_openharmony常用组件_Divider分割线
javascript·开源·harmonyos
Yanni4Night16 小时前
Parcel 作者:如何用静态Hermes把JavaScript编译成C语言
前端·javascript·rust
遇见~未来16 小时前
JavaScript构造函数与Class终极指南
开发语言·javascript·原型模式
cn_mengbei16 小时前
鸿蒙PC原生应用开发实战:ArkTS与DevEco Studio从零构建跨端桌面应用全栈指南
华为·wpf·harmonyos
毕设源码-邱学长16 小时前
【开题答辩全过程】以 基于VUE的打车系统的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
用户390513321928817 小时前
JS判断空值只知道“||”?不如来试试这个操作符
前端·javascript