Содержание
- Настройка проекта с использованием expo
- Понимание Hook useColorScheme
- Оформление текста в соответствии с цветовой схемой
- Переключение между светлой и темной темами динамически
- Создание нашего context для управления темой
- Извлечение и переключение темы с использованием компонента button
- Автоматическое адаптирование темы приложения к настройкам системы
- Заключение
Почти каждый веб-сайт и мобильное приложение теперь включают темный режим или альтернативную цветовую схему по умолчанию. Цветовые схемы предоставляют пользователям возможность выбирать дизайн приложения по своему усмотрению. Когда такая опция недоступна, это может разочаровать пользователей.
Цветовые схемы также помогают нам, разработчикам, проектировать для предпочтений каждого пользователя, то есть разрабатывать для пользователей, предпочитающих светлый или темный режим.
В этой статье мы не будем углубляться в дизайн и выбор цветов. Вместо этого мы сосредоточимся только на том, как реализовать переключатель тем в приложении React Native. Это означает переключение между светлым, темным и системным режимами, то есть цветовой схемой мобильного устройства.
Таким образом, наш акцент будет скорее на фрагментах кода и объяснениях вместе с их результатами. Мы рассмотрим:
Вы можете ознакомиться с полным кодом нашего демонстрационного приложения на GitLab.
Настройка проекта с использованием expo
Если вы предпочитаете разрабатывать приложения React Native с использованием Expo и собираетесь следовать этому учебнику с такой настройкой, вам придется внести небольшие изменения в ваш файл app.json.
В вашем файле app.json добавьте следующие строки;
{
"expo": {
"userInterfaceStyle": "automatic",
"ios": {
"userInterfaceStyle": "automatic"
},
"android": {
"userInterfaceStyle": "automatic"
}
}
}
По умолчанию установлен стиль light, поэтому наша цветовая схема всегда будет возвращать тему light. Вы можете изменить это на automatic, как показано выше, чтобы адаптироваться как к темной, так и к светлой теме. Это позволяет нам динамически получать цветовую схему нашего устройства.
Понимание Hook useColorScheme
Прежде чем мы начнем создавать наш переключатель тем, важно ознакомиться с Hook, с которым мы часто будем сталкиваться и использовать при реализации: useColorScheme.
useColorScheme - это Hook React Native, который позволяет нам подписываться на обновления различных цветовых схем. По сути, он предоставляет доступ к цветовой схеме устройства, которая может быть светлой или темной. Он возвращает значение, которое показывает текущую цветовую схему, предпочитаемую пользователем.
Рассмотрим следующий код:
/* App.js */
import React from 'react';
import { Text, StyleSheet, useColorScheme, View } from 'react-native';
const App = () => {
const colorScheme = useColorScheme();
return (
<View style={styles.container}>
<Text>Current Color Scheme: {colorScheme}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
Вывод будет отображать текущую цветовую схему. Например, в моем выводе показана темная цветовая схема, потому что это моя предпочтительная системная тема:

Если ваше устройство находится в светлом режиме, то в вашем выводе будет показана светлая цветовая схема.
Оформление текста в соответствии с цветовой схемой
С помощью значения, возвращаемого useColorScheme, мы можем проектировать при выборе пользователя темного или светлого режима. Давайте рассмотрим следующий фрагмент кода:
/* App.js */
import React from 'react';
import { Text, StyleSheet, useColorScheme, View } from 'react-native';
const App = () => {
const colorScheme = useColorScheme();
return (
<View style={styles.container}>
<Text
style={{
color: colorScheme === 'light' ? '#000' : '#fff',
}}
>
Current Color Scheme: {colorScheme}
</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
В приведенном выше коде мы оформляем наш текст в соответствии с цветовой схемой. Когда пользователи выбирают темный режим, мы оформляем текст белым цветом, чтобы он был виден в темном режиме. Напротив, мы оформляем текст черным цветом, когда пользователи находятся в светлом режиме.
Мы также можем использовать объект Color, предоставляемый react-native/Libraries/NewAppScreen, чтобы оформить наш текст. NewAppScreen - это компонент по умолчанию, который предоставляет нам React Native в качестве отправной точки для создания наших экранов. Он функционирует как шаблон и может использоваться, как показано ниже:
/* App.js */
import React from 'react';
import { Text, StyleSheet, useColorScheme, View } from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
const App = () => {
const colorScheme = useColorScheme();
const color = colorScheme === 'light' ? Colors.darker : Colors.lighter;
return (
<View style={styles.container}>
<Text style={{ color: color }}>Current Color Scheme: {colorScheme}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
В приведенном выше коде мы импортировали модуль NewAppScreen. Используя объект Color, который имеет набор предопределенных значений или Colors, мы назначаем цвет текста lighter, когда пользователь находится в темном режиме, и цвет darker, когда пользователь выбирает светлый режим:

Если мы изменим тему устройства на светлый режим, наш вывод будет выглядеть следующим образом:

Переключение между светлой и темной темами динамически
До сих пор мы изучили, как проверить текущий режим или цветовую схему нашего устройства. Мы также кратко рассмотрели, как использовать возвращаемое значение Hook useColorScheme для стилизации нашего приложения соответственно.
В этом разделе мы рассмотрим, как динамически переключаться между темами и как сохранять текущее состояние нашей темы.
Сначала давайте установим пакет async-storage. Этот пакет позволяет нам сохранять JSON-строки в локальном хранилище устройства. Он работает аналогично локальному хранилищу, сеансовому хранилищу и куки в Интернете:
/* npm */
npm install @react-native-async-storage/async-storage
/* yarn */
yarn add @react-native-async-storage/async-storage
/* expo */
npx expo install @react-native-async-storage/async-storage
В файле App.js скопируйте и вставьте следующий код:
/* App.js */
import React from 'react';
import Home from './src/Home';
import { ThemeProvider } from './context/ThemeContext';
const App = () => {
return (
<ThemeProvider>
<Home />
</ThemeProvider>
);
};
export default App;
В приведенном выше коде мы импортировали два компонента - ThemeContext и Home. Наш компонент ThemeContext будет содержать контекст нашей темы и текущее состояние, а Home будет нашей домашней страницей.
Мы оборачиваем страницу Home в ThemeContext, потому что мы хотим, чтобы темы были доступны для остальной части приложения.
Создание нашего context для управления темой
Затем создайте папку с именем context. Внутри этой папки создайте файл с именем ThemeContext.js с следующим кодом:
/* context/ThemeContext.js */
import React, { createContext, useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
useEffect(() => {
// Load saved theme from storage
const getTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem('theme');
if (savedTheme) {
setTheme(savedTheme);
}
} catch (error) {
console.log('Error loading theme:', error);
}
};
getTheme();
}, []);
const toggleTheme = (newTheme) => {
setTheme(newTheme);
AsyncStorage.setItem('theme', newTheme);
};
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
};
export default ThemeContext;
Мы управляем нашим состоянием и сохраняем текущее значение состояния в файле ThemeContext.js с использованием async-storage. Поскольку строки, переданные в локальное хранилище, остаются неизменными, пока не будут изменены или удалены, мы всегда можем извлечь последнее сохраненное значение и установить его как текущее состояние темы, как показано в приведенном выше коде.
Наконец, мы передаем наше состояние темы и функцию toggleTheme в ThemeContext.Provider. Это делает его доступным для остальной части приложения, так что мы можем вызывать функцию toggleTheme для выполнения наших переключений.
Извлечение и переключение темы с использованием компонента button
Далее давайте создадим файл Home.js и скопируем в него следующий код:
/* Home */
import React, { useContext } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import ThemeContext from '../context/ThemeContext';
const Home = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
const handleToggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
toggleTheme(newTheme);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme === 'dark' ? 'black' : 'white',
},
text: {
color: theme === 'dark' ? 'white' : 'black',
},
button: {
color: theme === 'dark' ? 'black' : 'white',
},
});
return (
<View style={styles.container}>
<Text style={styles.text}>Home page</Text>
<TouchableOpacity
onPress={handleToggleTheme}
style={{
marginTop: 10,
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: theme === 'dark' ? '#fff' : '#000',
}}
>
<Text style={styles.button}>Switch to {theme === 'light' ? 'Dark' : 'Light'} Theme</Text>
</TouchableOpacity>
</View>
);
};
export default Home;
В коде выше мы извлекаем значение theme и функцию toggleTheme, которые мы передали в наше API контекста. Используя возвращенное значение, мы можем стилизовать наши дизайны страниц в зависимости от значения theme. Мы также передаем нашу функцию toggleTheme в компонент button.
Вывод будет выглядеть следующим образом:

Автоматическое адаптирование темы приложения к настройкам системы
Мы увидели, как переключаться между темами, в частности, между светлой и темной темами. Последний шаг - обнаружить цветовую схему системы и точно переключить тему приложения соответственно.
Это означает, что если ваше мобильное устройство находится в светлом режиме, тема приложения также будет в светлом режиме. Затем, если вы переключите свою систему или мобильное устройство в темный режим, приложение автоматически адаптирует свое состояние темы к темной теме. Мы также обсудим сохранение состояния.
Для этого мы модифицируем код, который у нас уже есть выше. В файле ThemeContext.js скопируйте и вставьте следующий код:
/* ThemeContext */
import React, { createContext, useState, useEffect } from 'react';
import { useColorScheme } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const colorScheme = useColorScheme();
const [theme, setTheme] = useState(colorScheme || 'light');
useEffect(() => {
// Load saved theme from storage
const getTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem('theme');
if (savedTheme) {
setTheme(savedTheme);
}
} catch (error) {
console.log('Error loading theme:', error);
}
};
getTheme();
}, []);
useEffect(() => {
// set theme to system selected theme
if (colorScheme) {
setTheme(colorScheme);
}
}, [colorScheme]);
const toggleTheme = (newTheme) => {
setTheme(newTheme);
// Save selected theme to storage
AsyncStorage.setItem('theme', newTheme);
};
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
};
export default ThemeContext;
В приведенном выше коде, когда мы выбираем тему, мы также сохраняем ее в нашем локальном хранилище. Это означает, что при загрузке нашего приложения мы можем проверить, существует ли уже тема в нашем локальном хранилище. Если существует, то устанавливаем эту тему в качестве предпочтительной. В противном случае мы не устанавливаем никакой темы.
В итоге приложение для переключения темы в React Native будет выглядеть следующим образом:

В некоторых сценариях мы можем захотеть предоставить пользователям несколько вариантов тем, таких как:
- Темная тема
- Светлая тема
- Системная тема
Например, некоторые пользователи могут использовать светлую тему для своих мобильных устройств, но им может быть удобнее их приложение использовать с темной темой. В этом случае мы не хотим использовать светлую тему системы для приложения. Вместо этого мы хотим указать темную тему.
Для этого скопируйте следующий код в ваш файл ThemeContext.js:
/* ThemeContext.js */
....
const toggleTheme = (newTheme) => {
setTheme(newTheme);
AsyncStorage.setItem('theme', newTheme); // Save selected theme to storage
};
const useSystemTheme = () => {
setTheme(colorScheme);
AsyncStorage.setItem('theme', colorScheme);
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme, useSystemTheme }}>
{children}
</ThemeContext.Provider>
);
};
export default ThemeContext;
Первая функция позволяет нам переключаться между темными и светлыми темами, как мы видели в других примерах. Вторая функция позволяет нам использовать системную или тему по умолчанию устройства — мы устанавливаем нашу тему такой, какой она есть на устройстве в данный момент.
Затем в вашем файле Home.js мы создадим три кнопки для выбора каждой темы. Вот код:
/*Home.js*/
....imports....
const Home = () => {
const systemTheme = useColorScheme();
const { theme, toggleTheme, useSystemTheme } = useContext(ThemeContext);
const styles = StyleSheet.create({
......
});
return (
<View style={styles.container}>
<Text style={styles.text}>Current Theme: {theme}</Text>
<Text style={styles.text}>System Theme: {systemTheme}</Text>
<TouchableOpacity
onPress={() => toggleTheme('light')}
style={{
marginTop: 10,
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: theme === 'dark' ? '#fff' : '#000',
}}
>
<Text style={styles.button}>Light Theme</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => toggleTheme('dark')}
style={{
marginTop: 20,
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: theme === 'dark' ? '#fff' : '#000',
}}
>
<Text style={styles.button}>Dark Theme</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => useSystemTheme()}
style={{
marginTop: 20,
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: theme === 'dark' ? '#fff' : '#000',
}}
>
<Text style={styles.button}>System Theme</Text>
</TouchableOpacity>
</View>
);
};
export default Home;
С этими тремя кнопками мы можем выбирать любую тему. Код выше также позволяет нам видеть текущую тему нашего приложения — другими словами, тему, которую мы выбрали в данный момент — а также тему системы, которая может отличаться от текущей темы.
С этим мы можем либо выбрать использовать тему системы автоматически, либо выбрать другую тему для нашего приложения:

Как видите, наша выбранная тема будет сохраняться даже после закрытия и повторного открытия приложения.
Заключение
В этой статье мы увидели, как реализовать функцию переключения, сохранить состояние каждого выбора и переключиться между темами на основе выбора пользователя по системе.
Переключение между темами — это обычная функция в разработке приложений сегодня. Предоставление пользователям выбора их предпочтительной темы и сохранение ее даже после закрытия приложения улучшает пользовательский опыт и делает ваше приложение более привлекательным.
Полный код проекта доступен на GitLab. Если у вас остались вопросы, не стесняйтесь задавать их ниже. В противном случае, до следующей статьи, пока!