ReactNative主题适配(深浅模式)
在移动应用开发中,主题系统已成为提升用户体验的核心要素。据统计,超过85%的iOS用户和76%的Android用户启用深色模式,而企业品牌应用中93%需要定制化主题支持。React Native的主题配置不仅能实现设计一致性,还能显著提升用户留存率(平均+17%)和开发效率(代码复用率提升40%)
Expo 作为 React Native 的增强框架,为开发者提供了开箱即用的主题解决方案,本文将深入探讨如何利用 Expo 生态系统实现高效、动态的主题配置系统
配置
对于 Android
和 iOS
项目,需要Expo配置来支持在光模式和黑暗模式之间进行切换。对于Web不需要其他配置
打开项目根目录下的 app.json
文件:
{
"expo": {
"userInterfaceStyle": "automatic"
}
}
2
3
4
5
您还可以通过设置 android.userinterfaceStyle
或 ios.userinternterfacestyle
将 userInterfaceStyle
属性配置为特定平台
需要注意当你适配 Android
系统时,必须安装 expo-system-ui 以支持Android的外观样式。否则,将忽略 userinternterfacestyle
属性
➜ npx expo install expo-system-ui
userinterfaceStyle
UserInterFaceStyle属性支持以下值:
- automatic:跟随系统外观设置,并通知用户的任何更改
- dark:限制应用程序仅支持深色主题
- light:限制应用程序仅支持浅色主题
配色方案
要检测项目中的配色方案,可以使用ReactNative的Appearance
或useColorScheme
:
let colorScheme = useColorScheme();
if (colorScheme === 'dark') {
// render some dark thing
} else {
// render some light thing
}
2
3
4
5
6
除此之外Appearance.getColorScheme()
和Appearance.AddChangeListener()
也是很重要,根据以上提供的方法我们可以设计2种简单的方案
方案一
方案一则在组件内部直接采用三元运算计算当前主题的颜色配置,如:
import { Text, StyleSheet, View, useColorScheme } from 'react-native';
import { StatusBar } from 'expo-status-bar';
export default function App() {
const colorScheme = useColorScheme();
const themeTextStyle = colorScheme === 'light' ? styles.lightThemeText : styles.darkThemeText;
const themeContainerStyle =
colorScheme === 'light' ? styles.lightContainer : styles.darkContainer;
return (
<View style={[styles.container, themeContainerStyle]}>
<Text style={[styles.text, themeTextStyle]}>Color scheme: {colorScheme}</Text>
<StatusBar />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 20,
},
lightContainer: {
backgroundColor: '#d0d0c0',
},
darkContainer: {
backgroundColor: '#242c40',
},
lightThemeText: {
color: '#242c40',
},
darkThemeText: {
color: '#d0d0c0',
},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
我们使用useColorScheme
这个函数直接监测到主题的值,然后再拿到对样的样式,就可以适配主题的切换显示
这种方式通常针对一些比较特殊的颜色配置,如果对弈通用的颜色配置,如果都采用此种方式来做,那主题的可维护性或可扩展性就太差了
方案二
方案二则是采用以主题为组件扩展,通过开发所有的有共性的主题组件,那么当主题切换时其内部会自动监听到变化,从而显示对应的颜色配置
可以先维护一组基础的颜色常量,用来配置一些共性的颜色配置
const tintColorLight = '#0a7ea4';
const tintColorDark = '#fff';
export const Colors = {
light: {
text: '#11181C',
background: '#fff',
tint: tintColorLight,
icon: '#687076',
tabIconDefault: '#687076',
tabIconSelected: tintColorLight,
},
dark: {
text: '#ECEDEE',
background: '#151718',
tint: tintColorDark,
icon: '#9BA1A6',
tabIconDefault: '#9BA1A6',
tabIconSelected: tintColorDark,
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里用Colors
来存放App的所有基础颜色常量,以light
、dark
进行划分,当然如果App有多种颜色配置要求,可以以此为基础进行扩展
那么我们如何拿到对应的主题下的颜色常量呢,可以写一个辅助函数来获取:
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
const theme = useColorScheme() ?? 'light';
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
useThemeColor
内部还是通过useColorScheme
拿到当前App的主题,然后从Colors
中拿到最终变量;为了灵活性可以通过props
指定当前具体的不同主题下的颜色
这里我们以Text为例,创建一个包含主题的Text组件 ThemedText
:
export function ThemedText({
style,
lightColor,
darkColor,
...rest
}: ThemedTextProps) {
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
return (
<Text
style={[{ color }, style]}
{...rest}
/>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
那么就可以在具体的页面或组件中使用:
export default function MePage() {
return <ThemedText>
React Native
</ThemedText>
}
2
3
4
5
这样在主题变化时内部就会自动监测到变化从而正确更新最终的颜色。当然了上面的组件非常简单,读者完全可以根据业务做一些最基本的样式配置,搭配props可以做好横向扩展
秉着万物皆组件的思想,其他的组件逻辑类似,这样以来颜色主题配置会更加方便维护和扩展
持久化
通常App的深浅模式会给用户一个选项,如:深色、显色、跟随系统等。如果是跟随系统那么直接使用useColorScheme
以上的方案完全不需要调整,而如果用户单独设置了浅色、深色或者其他模式呢?那么就需要将主题持久化处理了
持久化我们在往期快速入门中讲过,对于这种字符串数据完全可以采用AsyncStorage处理,类似于web的localStorage,这里就不多说了