Expo bareExpo RouterReact Context APIReact NativeReact Native PaperTypeScript Light/Dark Theme

[How-to] Light・Dark テーマを手動で切り替える実践方法〜React Native Paper〜

jogging-4211946_1280 Expo bare
スポンサーリンク

開発環境

Expo Router 環境です。詳細は ⬇︎

app/ フォルダー構造

try🐶everything myproject$ tree app
app
├── +html.tsx
├── [...missing].tsx
├── _layout.tsx
└── index.tsx

src/ フォルダー構造

try🐶everything myproject$ tree src
src
├── common
│   ├── hooks
│   │   ├── index.ts
│   │   ├── themeContext.ts
└── components
    ├── ExternalLink.tsx
    └── index.ts

try🐶everything myproject$ 

実装する

ThemeContext を作成する

コンテキストを作成するには、コンポーネントの外部で createContext を呼び出します。

// src/common/hooks/themeContext.ts

import {createContext} from 'react';

export interface ThemeContextProps {
  preferredTheme: string;   // <-- preferredTheme は任意
  toggleTheme: () => {};       // <-- toggleTheme は任意
}


export const ThemeContext = createContext({   // <-- ThemeContext は任意
  preferredTheme: 'light',
  toggleTheme: () => {},
});

コンポーネントをラップする

コンポーネントをコンテキスト プロバイダーにラップして、内部のすべてのコンポーネントにこのコンテキストの値を指定します。

toggleTheme() を実行する度に、preferredTheme 値がトグルされ、paperTheme 値も更新されます。

ThemeContext.Providervalue 値として preferences を渡します。

ラップする際の注意点は、Line 17,18 のように PaperProvider もラップすることです。

// app/_layout.tsx

...
import {ThemeContext} from 'common/hooks/themeContext';
...

export default function RootLayout() {
  const [preferredTheme, setTheme] = React.useState('light');
  const toggleTheme = () => {
    setTheme(preferredTheme => (preferredTheme === 'light' ? 'dark' : 'light'));
  };
  const preferences = React.useMemo(
    () => ({preferredTheme, toggleTheme: toggleTheme}),
    [preferredTheme, toggleTheme],
  );

  const paperTheme =
    preferredTheme === 'dark'
      ? {...MD3DarkTheme, colors: jtheme.dark.colors}
      : {...MD3LightTheme, colors: jtheme.light.colors};

  ...
  return (
    <ThemeContext.Provider value={preferences}>
      <PaperProvider theme={paperTheme}>
        <RootLayoutNav />
      </PaperProvider>
    </ThemeContext.Provider>
  );
}

function RootLayoutNav() {
  return (
    <Stack>
      <Stack.Screen name="index" />
      ...
    </Stack>
  );
}

テーマをトグルする

メニューを実装するファイルで行ってください。

backgroundColor は変わらないので Line 15 で設定を加えています。(仕様?不明!)

// app/index.tsx

 // コピペで使用可能

import React, {memo} from 'react';
import {StyleSheet, View} from 'react-native';
import {Button, useTheme} from 'react-native-paper';
import {Ionicons} from '@expo/vector-icons';
import {ThemeContext} from 'common/hooks/themeContext';

const HomeScreen = () => {
  const theme = useTheme();
  const {preferredTheme, toggleTheme} = React.useContext(ThemeContext);

  return (
    <View
      style={[styles.container, {backgroundColor: theme.colors.background}]}>
      <View style={{position: 'absolute', top: 0, right: 0, margin: 20}}>
        {preferredTheme == 'dark' ? (
          <Ionicons
            name="sunny"
            size={24}
            color={theme.colors.tertiary}
            onPress={toggleTheme}
          />
        ) : (
          <Ionicons
            name="moon"
            size={24}
            color={theme.colors.tertiary}
            onPress={toggleTheme}
          />
        )}
      </View>
      <Button
        icon="camera"
        mode="contained"
        onPress={() => console.log('Pressed!')}>
        Press me
      </Button>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {flex: 1, alignItems: 'center', justifyContent: 'center'},
});
export default memo(HomeScreen);

ボタンを押しながら動作を確認できます。

参考文献

createContext()

Theming // <– ⬆︎ コード内の jtheme.{dark|light}.colors を作成する方法

スポンサーリンク

コメント

タイトルとURLをコピーしました