rn_for_openharmony商城项目app实战-主题设置实现

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy

写在前面

深色模式这几年特别火,iOS 13 和 Android 10 都加入了系统级的深色模式支持。作为一个现代 App,不支持深色模式好像有点说不过去。

我自己是深色模式的重度用户,晚上躺床上刷手机的时候,浅色界面简直亮瞎眼。所以每次装新 App,第一件事就是去设置里找有没有深色模式。如果没有,好感度直接减一半。

这篇文章记录一下主题设置页面的实现过程。页面本身和语言设置差不多,就是一个选择列表。但深色模式的实现比多语言复杂一些,涉及到整个 App 的样式切换。

支持哪些主题

先定义支持的主题选项:

tsx 复制代码
const themes = [
  {id: 'system', name: '跟随系统', icon: '📱', desc: '自动适应系统深浅色模式'},
  {id: 'light', name: '浅色模式', icon: '☀️', desc: '始终使用浅色主题'},
  {id: 'dark', name: '深色模式', icon: '🌙', desc: '始终使用深色主题'},
];

三个选项:

跟随系统:系统是深色模式,App 就是深色;系统是浅色模式,App 就是浅色。这是最省心的选项,大部分用户会选这个。

浅色模式:不管系统是什么模式,App 始终是浅色。有些用户就是喜欢浅色,或者觉得深色模式看着累。

深色模式:不管系统是什么模式,App 始终是深色。晚上用手机的用户会喜欢这个。

每个选项有四个属性:id 是标识符,name 是显示名称,icon 是图标,desc 是描述文字。描述文字能帮助用户理解每个选项的含义。

引入需要的依赖

tsx 复制代码
import React from 'react';

不需要本地状态,主题设置存在全局状态里。

tsx 复制代码
import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native';

和语言设置页面用的组件一样。设置类页面的结构都差不多,可以复用很多代码。

tsx 复制代码
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';

获取全局状态

tsx 复制代码
export const ThemeScreen = () => {
  const {settings, updateSettings, goBack} = useApp();

和语言设置一样,从全局状态里取 settingsupdateSettingsgoBack

选择主题的逻辑

tsx 复制代码
const handleSelect = (id: string) => {
  updateSettings('theme', id);
  Alert.alert('设置成功', '主题设置已更新', [{text: '确定', onPress: goBack}]);
};

逻辑很简单:更新全局状态里的 theme 字段,弹个提示,返回上一页。

实际项目中,切换主题后应该立即生效,整个 App 的颜色都要变。这需要配合主题系统来实现,后面会详细讲。

页面结构

tsx 复制代码
return (
  <View style={styles.container}>
    <Header title="主题设置" />

    <View style={styles.content}>
      {themes.map(theme => (
        // 主题选项
      ))}
    </View>

    <Text style={styles.tip}>
      💡 深色模式可以在夜间使用时减少眼睛疲劳,同时节省电量
    </Text>
  </View>
);

比语言设置多了一个底部提示,告诉用户深色模式的好处。

渲染主题选项

tsx 复制代码
{themes.map(theme => (
  <TouchableOpacity 
    key={theme.id} 
    style={styles.item} 
    onPress={() => handleSelect(theme.id)}
  >
    <View style={styles.iconWrap}>
      <Text style={styles.icon}>{theme.icon}</Text>
    </View>

左边是图标,用一个圆形背景包裹,看起来更精致。

tsx 复制代码
    <View style={styles.info}>
      <Text style={styles.themeName}>{theme.name}</Text>
      <Text style={styles.themeDesc}>{theme.desc}</Text>
    </View>

中间是主题名称和描述。描述用小号灰色字,补充说明这个选项的作用。

tsx 复制代码
    {settings.theme === theme.id && <Text style={styles.check}>✓</Text>}
  </TouchableOpacity>
))}

右边是选中标记,和语言设置一样的逻辑。

样式定义

typescript 复制代码
const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: '#f5f5f5'},
  content: {flex: 1, backgroundColor: '#fff', marginTop: 12},

基础布局,灰色背景上放白色列表。

typescript 复制代码
  item: {
    flexDirection: 'row', 
    alignItems: 'center', 
    padding: 16, 
    borderBottomWidth: 1, 
    borderBottomColor: '#f0f0f0'
  },

每个选项横向排列,底部有分割线。

typescript 复制代码
  iconWrap: {
    width: 44, 
    height: 44, 
    borderRadius: 22, 
    backgroundColor: '#f5f5f5', 
    justifyContent: 'center', 
    alignItems: 'center', 
    marginRight: 12
  },
  icon: {fontSize: 24},

图标用圆形灰色背景包裹,44x44 的大小,比语言设置的国旗更精致一些。

typescript 复制代码
  info: {flex: 1},
  themeName: {fontSize: 16, color: '#333', fontWeight: '500'},
  themeDesc: {fontSize: 12, color: '#999', marginTop: 2},

名称用 16 号字加粗,描述用 12 号灰色字。

typescript 复制代码
  check: {fontSize: 20, color: '#3498db', fontWeight: 'bold'},
  tip: {fontSize: 13, color: '#999', padding: 16, lineHeight: 20},
});

底部提示用灰色小字,不要太醒目。

深色模式的实现方案

上面只是做了主题设置的 UI,真正的深色模式实现需要更多工作。聊一下常见的方案:

方案一:React Native 内置的 useColorScheme

React Native 提供了 useColorScheme hook,可以获取系统当前的颜色模式:

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

const colorScheme = useColorScheme(); // 'light' | 'dark' | null

这个 hook 会返回 'light''dark'nullnull 表示系统不支持或者无法确定。

配合我们的主题设置,可以这样判断当前应该用什么主题:

tsx 复制代码
const systemTheme = useColorScheme();
const currentTheme = settings.theme === 'system' 
  ? (systemTheme || 'light') 
  : settings.theme;

如果用户选了"跟随系统",就用系统主题;否则用用户选择的主题。

方案二:styled-components 的 ThemeProvider

如果用 styled-components,可以用 ThemeProvider 来管理主题:

tsx 复制代码
const lightTheme = {
  background: '#ffffff',
  text: '#333333',
  primary: '#3498db',
};

const darkTheme = {
  background: '#1a1a1a',
  text: '#ffffff',
  primary: '#5dade2',
};

<ThemeProvider theme={currentTheme === 'dark' ? darkTheme : lightTheme}>
  <App />
</ThemeProvider>

然后在组件里通过 props.theme 获取当前主题的颜色值。

方案三:React Context 自己实现

不用第三方库,自己用 Context 实现也可以:

tsx 复制代码
const ThemeContext = React.createContext({
  theme: 'light',
  colors: lightColors,
});

// 在组件里使用
const {colors} = useContext(ThemeContext);
<View style={{backgroundColor: colors.background}}>

这个方案灵活性最高,但需要自己处理的东西也最多。

我们这个项目为了简化,没有真正实现深色模式切换,只是保存了主题设置。实际项目中建议用方案一或方案三。

深色模式的设计要点

做深色模式不是简单地把白色换成黑色,有很多细节要注意:

1. 不要用纯黑色

纯黑色(#000000)在 OLED 屏幕上和其他颜色对比太强烈,看着不舒服。建议用深灰色,比如 #1a1a1a 或 #121212。

Google 的 Material Design 推荐深色模式的背景色是 #121212,这个颜色经过了大量测试,视觉效果比较好。

2. 文字颜色要降低对比度

浅色模式下文字是黑色(#333333),深色模式下不要用纯白色(#ffffff),用稍微暗一点的白色(#e0e0e0)。

对比度太高会让眼睛疲劳,特别是在暗环境下。

3. 主题色可能需要调整

有些颜色在浅色背景上好看,在深色背景上可能不好看。比如我们的主题蓝色 #3498db,在深色背景上可能需要调亮一点。

4. 图片和图标

有些图片在深色背景上可能看不清,需要准备深色模式专用的图片。图标如果是深色的,在深色背景上也会看不清。

一个技巧是给图标加个浅色的背景或者边框。

5. 阴影效果

浅色模式下常用的阴影效果,在深色模式下基本看不出来。可以用边框或者微妙的颜色差异来代替。

跟随系统的实现

"跟随系统"选项需要监听系统主题变化:

tsx 复制代码
import {useColorScheme, Appearance} from 'react-native';

// 方式一:用 hook(推荐)
const colorScheme = useColorScheme();

// 方式二:监听变化
useEffect(() => {
  const subscription = Appearance.addChangeListener(({colorScheme}) => {
    console.log('系统主题变了:', colorScheme);
  });
  return () => subscription.remove();
}, []);

useColorScheme 会自动响应系统主题变化,组件会重新渲染。如果需要在变化时执行一些逻辑(比如保存到本地),可以用 Appearance.addChangeListener

主题切换的动画

切换主题时,如果颜色瞬间变化会很突兀。可以加个过渡动画:

tsx 复制代码
// 简单的方案:用 LayoutAnimation
import {LayoutAnimation} from 'react-native';

const handleSelect = (id: string) => {
  LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  updateSettings('theme', id);
};

LayoutAnimation 会让布局变化有个平滑的过渡效果。不过它对颜色变化的支持有限,效果可能不太明显。

更好的方案是用 react-native-reanimated 做颜色插值动画,但实现起来比较复杂。

省电效果

深色模式在 OLED 屏幕上确实能省电,因为 OLED 显示黑色时像素是关闭的,不耗电。

但在 LCD 屏幕上,深色模式不省电,甚至可能更耗电(因为背光始终亮着,显示深色需要液晶遮挡更多光线)。

所以底部提示里说"节省电量",严格来说只对 OLED 屏幕成立。不过现在大部分中高端手机都是 OLED 屏幕了,这么说问题不大。

小结

主题设置页面的 UI 和语言设置很像,就是一个选择列表。但深色模式的实现比多语言复杂。

几个关键点:

  • 提供三个选项:跟随系统、浅色、深色
  • 每个选项有图标和描述,帮助用户理解
  • 底部提示深色模式的好处

关于深色模式实现:

  • useColorScheme 获取系统主题
  • 不要用纯黑色,用深灰色
  • 文字颜色要降低对比度
  • 图片和图标可能需要适配
  • 切换时可以加过渡动画

深色模式是个细活,做好了用户体验会很好,做不好会很丑。如果时间有限,可以先只支持浅色模式,后面再加深色模式。

下一篇写隐私政策页面,这是本系列的最后一篇,敬请期待。


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

相关推荐
heartbeat..2 小时前
SQL 常用函数大全:聚合、字符串、数值、日期、窗口函数解析
java·数据库·sql·函数
m0_471199632 小时前
【场景】如何快速接手一个前端项目
前端·vue.js·react.js
编程之路从0到12 小时前
ReactNative新架构之Android端TurboModule机制完全解析
android·react native·源码阅读
chuxinweihui2 小时前
MySQL数据库基础
数据库·mysql
lili-felicity2 小时前
React Native for Harmony 个人消息列表最新消息置顶实现(多维度权重统计)
javascript·react native·react.js
Tigger2 小时前
用 Vue 3 做了一套年会抽奖工具,顺便踩了些坑
前端·javascript·vue.js
天天扭码2 小时前
一文搞懂——React 19到底更新了什么
前端·react.js·前端框架
OpenTiny社区3 小时前
OpenTiny 2025年度贡献者榜单正式公布~
前端·javascript·vue.js
OEC小胖胖3 小时前
08|Commit 阶段:副作用如何被组织、执行与约束
前端·react.js·前端框架·react·开源库