React Nativeのダークモードの設定方法【Expo】

私が開発しているアプリLangJournalではDarkMode(ダークモード)に対応しております。本日はダークモードをReact Nativeでどのように実装すれば良いかを記載していきます。

最終的なゴールは、下記二つです。

・端末の設定によりダークモードとライトモードを自動で切り替えることができる
・ユーザがダークモードとライトモードをアプリ内で選ぶことができる

一つずつ解説していきます。

カラーの作成

まずは、react-native-paperをインストールします。

Terminal
yarn add react-native-paper

react-native-paperは色を保持するために使います。例えば、ライトモードの時のbackgroundは#fff、textは#000、ダークモードの時のbackgroundは#000、textは#fffといったような感じです。

同じことができるパッケージは複数あります。react-navigationも検索すると上位に出てきますが、こちらはオリジナルな変数を持てません。primary, backgroundなど決まった変数しか持てないので、使い勝手が悪いです。react-native-paperを使うのが無難でしょう。

以下は具体的なコードです。必要最小限なものだけ記載します。

styles.ts
import { useTheme } from 'react-native-paper';

const black = '#2a2a2a';
const white = '#fff';
const main = '#2D9CDB';

// ダークモード、ライトモード共有で使うものはまとめておくと分かりやすい
const commonColor = {
  main,
  // ここに他の色も追加していく。
};

// ライトテーマ
export const lightTheme = {
  dark: false, // 一番最後にステータスバーの設定の時に使う
  colors: {
    ...commonColor,
    background: white,
    primary: black,
   // ここに他の色も追加していく。
  },
};

// ダークテーマ
export const darkTheme = {
  dark: true, // 一番最後にステータスバーの設定の時に使う
  colors: {
    ...commonColor,
    background: black,
    primary: white,
    // ここに他の色も追加していく。
  },
};

// TypeScript用
export type AppTheme = typeof lightTheme | typeof darkTheme;

// useThemeのままだと、オリジナル変数を呼ぶときにTypeScriptでエラーになるので、
// useAppThemeを別途用意した方がいい
export const useAppTheme = () => useTheme<AppTheme>();

これでカラーの設定は完了です。

表示設定画面の作成

続いて、ダークモードのON/OFFを切り替えられる画面を作ります。私は下記のようなUIにしております。

LangJournalでは、「端末の設定を使う」「ライトモード」「ダークモード」の3つを用意しております。TwitterやNoteなどのアプリを確認したところ、上記の3つが設定できるものが多かったです。

実装方法は自由ですが、上記変数をReduxもしくはContextで持てるようにしましょう。コードはプロジェクトによって違いがありすぎるのと今回の記事のテーマとは関係ないので割愛します。

App.tsxの実装

続いて、全体を囲うファイルを修正していきます。App.tsxなどで書いているプロジェクトが多いのではないでしょうか。

まずはコードから

App.tsx
import { useColorScheme } from 'react-native';

// 中略

const theme = useMemo(() => {
    if (!localStatus.darkMode || localStatus.darkMode === 'device') {
      // 端末の設定を使う を選んでいる時
      if (scheme === 'light') {
        return lightTheme;
      } else {
        return darkTheme;
      }
    } else if (localStatus.darkMode === 'light') {
      return lightTheme;
    } else {
      return darkTheme;
    }
  }, [localStatus.darkMode, scheme]);

useColorSchemeは端末のダークモード、ライトモードを取得できます。

localStatus.darkModeは表示設定画面で設定した値です。私はReduxで持っているため上記のようなコードになっております。

続いて、全体をラップします。上記で書いたものと同じファイルに追記します。

App.tsx
import { useColorScheme } from 'react-native';
import { PaperProvider } from 'react-native-paper'; // 追加

// 中略

const theme = useMemo(() => {
    if (!localStatus.darkMode || localStatus.darkMode === 'device') {
      if (scheme === 'light') {
        return lightTheme;
      } else {
        return darkTheme;
      }
    } else if (localStatus.darkMode === 'light') {
      return lightTheme;
    } else {
      return darkTheme;
    }
  }, [localStatus.darkMode, scheme]);

<PaperProvider theme={theme}> // ここに追加
  <NavigationContainer>
    <Stack.Navigator>
     // プロジェクトにより違うため省略
    </Stack.Navigator>
  </NavigationContainer>
</PaperProvider>

// ここに追加 // プロジェクトにより違うため省略

先ほど設定したthemeをPaperProviderに設定します。

themeを取得し、色を指定する

ここまで実装が終われば、色をダークモードとライトモードで切り分けることができます。下記のように使います。

Text.tsx
// 中略
import { useAppTheme } from '@/styles/colors';

const Text: React.FC = () => {
  const theme = useAppTheme(); // このように呼び出す
  return (
    <Text
      style={{color: color ? color : theme.colors.primary}} //このように指定する
    >
      Test
    </Text>
  );
};

あとは、地道に色を指定している箇所を書き換えていくだけです。できる限り共通コンポーネント化して、色を指定する箇所が少なくなるようにしましょう。

StatusBarの設定

最後にStatusBarの設定です。StatusBarもダークモードで切り替える必要があります。

まずは、expo-status-barをインストールしてください。

Terminal
expo install expo-status-bar

※Expoを使っていない場合は、react-nativeのStatusBarを使いましょう。以下は、Expoを使っている前提で説明していきますが、react-nativeのStatusBarに置き換えて実装してください。

App.tsx
import { useColorScheme } from 'react-native';
import { PaperProvider } from 'react-native-paper'; 
import { StatusBar } from 'expo-status-bar'; // 追加

// 中略

const theme = useMemo(() => {
    if (!localStatus.darkMode || localStatus.darkMode === 'device') {
      if (scheme === 'light') {
        return lightTheme;
      } else {
        return darkTheme;
      }
    } else if (localStatus.darkMode === 'light') {
      return lightTheme;
    } else {
      return darkTheme;
    }
  }, [localStatus.darkMode, scheme]);

<PaperProvider>
  <NavigationContainer>
    <StatusBar // ここに追加
      backgroundColor={theme.colors.background}
      style={theme.dark ? 'light' : 'dark'}
    />
    <Stack.Navigator>
     // 省略
    </Stack.Navigator>
  </NavigationContainer>
</PaperProvider>

theme.darkは、styles.tsで設定した値です。StatusBarのstyleにlight or darkを設定することで色を変えることができます。

以上で、ダークモードの設定完了です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です