等待是最难熬的
点了一个按钮,然后什么反应都没有。你会想:点到了吗?要不要再点一下?是不是卡住了?
这种不确定感让人焦虑。加载状态就是为了消除这种焦虑。它告诉用户"我收到你的请求了,正在处理,请稍等"。
我们的 TodoList 应用是本地数据,不需要网络请求,加载几乎是瞬间完成的。但我们还是实现了加载状态,一方面是为了演示,另一方面是为了让应用感觉更完整。下拉刷新时显示"刷新中...",让用户知道刷新正在进行。
刷新时的加载提示
我们在下拉刷新时显示一个文字提示:
tsx
{refreshing && <View style={styles.loadingOverlay}><Text style={styles.loadingText}>刷新中...</Text></View>}
这是条件渲染。只有当 refreshing 为 true 时,加载提示才会显示。
刷新状态的管理
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
