【React Native】React Native PaperでUI作成

## React Native Paperとは
GoogleのMaterial Designガイドラインに基づいた
React Native用のUIコンポーネントライブラリです。
美しく一貫性のあるデザインを簡単に実装できるよう設計されている。

https://callstack.github.io/react-native-paper/

### React Nativeの標準コンポーネントとの違い
#### 標準コンポーネント
- 基本的な機能のみを提供
- デザインは最小限でプラットフォーム固有の見た目
- UIのスタイリングは全て自分で行う
#### React Native Paper
- デザイン済の高機能コンポーネント
- Material Designガイドラインに従った一貫したデザイン
- テーマ機能によるアプリ全体の色や字体の統一
- アニメーションや状態管理が組み込み済み
### React Native Paperを使うメリット
**開発の効率化：**
1つのコードベースでiOSとAndroidの両方に対応できるのと、
デザイン済のコンポーネントを使うことでUI開発の時間が大幅に短縮される。

**デザインの一貫性：**
すべてのコンポーネントが Material Design に従っているため、
統一感のあるデザインが簡単に実現できます

**テーマ機能：**
アプリ全体のカラーテーマやフォントを簡単に変更できる。

**ダークモード対応：**
標準でダークモードをサポートしている。

**アクセシビリティ**
アクセシビリティ機能が標準で組み込まれている。

## 利用準備
１．インストール
```
npm install react-native-paper 
```

２．開発環境
**Expo：** 52.0.46
**react-native：** 0.76.9
**react-native-paoer：** 5.13.1

３．レイアウトコンポーネント追記
```tsx:_layout.tsx
const Layout = () => {
    return (
        <PaperProvider>
            <Stack screenOptions={{ headerShown: false }} />
        </PaperProvider>
    )
}

export default Layout
```

４．実装例
```tsx:paperui_sample.tsx
import { router } from "expo-router"
import { StatusBar } from "expo-status-bar"
import { useState } from "react"
import { StyleSheet, View } from "react-native"
import { ScrollView } from "react-native-gesture-handler"
import { ActivityIndicator, Appbar, Avatar, Banner, Button, Checkbox, Divider, RadioButton, SegmentedButtons, Snackbar, Switch, Text, TextInput, useTheme } from "react-native-paper"


const PaperUISample = () => {
    const theme = useTheme()
    const [text, setText] = useState('')
    const [isLoading, setIsLoading] = useState(false)
    const [switchValue, setSwitchValue] = useState(false)
    const [checkboxStatus, setCheckboxStatus] = useState(false)
    const [radioValue, setRadioValue] = useState('')
    const [segmentValue, setSegmentValue] = useState('')
    const [snackbarVisible, setSnackbarVisible] = useState(false)
    const [bannerVisible, setBannerVisible] = useState(false)

    return (
        <View style={styles.container}>
            <StatusBar style="auto" />
            
            {/* 画面上部のナビゲーションバー */}
            {/* BackActionは戻るボタン */}
            {/* Contentはタイトル（画面名など）を表示 */}
            <Appbar.Header>
                <Appbar.BackAction onPress={() => router.back()} />
                <Appbar.Content title="UI Sample" />
            </Appbar.Header>
            
            {/* バナー表示 */}
            {/* visible：表示／非表示制御、actions：ボタンの配列、icon：バナーに表示するアイコン */}
            <Banner
                visible={bannerVisible}
                actions={[
                    {
                        label: '確認',
                        onPress: () => setBannerVisible(false)
                    },
                    {
                        label: '閉じる',
                        onPress: () => setBannerVisible(false)
                    }
                ]}
                icon={() => (
                    <Avatar.Icon size={40} icon='alert' />
                )}
            >
                バナーを開くとこちらの情報が表示されます。
            </Banner>

            {isLoading ? (
                <View style={styles.loadingContainer}>
                    {/* インジケーター */}
                    {/* size：'small' or 'large'で指定、animating：アニメーション有無、color：インジケーターの色 */}
                    <ActivityIndicator size="large" animating={true} color={theme.colors.primary} />
                    <Text style={[styles.loadingText, { color: theme.colors.primary }]} variant='labelLarge' >loading...</Text>
                    <Button
                        mode="outlined"
                        onPress={() => setIsLoading(false)}
                    >
                        戻る
                    </Button>
                </View>
            ) : (
                <ScrollView style={styles.scrollView}>
                    {/* テキスト入力 */}
                    <View style={styles.section}>
                        {/* ラベル表示 */}
                        {/* variant：ラベルのフォントサイズ */}
                        <Text variant='titleMedium' style={styles.sectionTitle}>
                            テキスト入力
                        </Text>

                        {/* テキスト入力フィールド */}
                        <TextInput
                            label='テキスト入力できます'
                            value={text}
                            onChangeText={text => setText(text)}
                        />
                    </View>
                    <Divider style={styles.divider} />
                    <View style={styles.section}>
                        <Text variant='titleMedium' style={styles.sectionTitle}>基本コンポーネント</Text>
                        <View style={styles.row}>
                            <Text variant='titleMedium'>スイッチ</Text>
                            
                            {/* on/offを切り替えるスイッチ */}
                            <Switch
                                value={switchValue}
                                onValueChange={() => setSwitchValue(!switchValue)}
                            />
                        </View>
                        <View style={styles.row}>
                            <Text variant="titleMedium">チェックボックス</Text>
                            {/* チェックボックス */}
                            <Checkbox
                                status={checkboxStatus ? "checked" : "unchecked"}
                                onPress={() => setCheckboxStatus(!checkboxStatus)}
                            />
                        </View>

                        <Text variant='titleMedium' style={styles.sectionTitle} >ラジオボタン</Text>

                        {/* ラジオボタン */}
                        {/* Group：ラジオボタングループ化 */}
                        {/* 各RadioButtonには一意のvalueを指定 */}
                        <RadioButton.Group
                            onValueChange={value => setRadioValue(value)}
                            value={radioValue}
                        >
                            <View style={styles.radioContainer}>
                                <View style={styles.radioRow}>
                                    <RadioButton value="1" />
                                    <Text>Radio 1</Text>
                                </View>
                                <View style={styles.radioRow}>
                                    <RadioButton value="2" />
                                    <Text>Radio 2</Text>
                                </View>
                            </View>
                        </RadioButton.Group>

                        <Text variant='titleMedium' style={styles.sectionTitle}>セグメントボタン</Text>

                        {/* セグメント選択UI */}
                        <SegmentedButtons
                            value={segmentValue}
                            onValueChange={setSegmentValue}
                            buttons={[
                                { value: 'walk', label: '散歩', icon: 'walk' },
                                { value: 'train', label: '電車', icon: 'train' },
                                { value: 'car', label: '車', icon: 'car' }

                            ]}
                        />
                    </View>

                    <Divider style={styles.divider} />

                    <View style={styles.section}>
                        <Text variant='titleMedium' style={styles.sectionTitle}>アクション</Text>

                        {/* ボタン */}
                        {/* mode： テキスト、塗りつぶし、枠線付きなど */}
                        <Button
                            mode='contained'
                            onPress={() => setIsLoading(true)}
                            style={styles.button}
                        >
                            読み込み開始
                        </Button>
                        <Button
                            mode='contained'
                            onPress={() => setBannerVisible(true)}
                            style={styles.button}
                        >
                            バナー表示
                        </Button>
                        <Button
                            mode='contained'
                            onPress={() => setSnackbarVisible(true)}
                            style={styles.button}
                        >
                            スナックバー表示
                        </Button>
                    </View>
                </ScrollView>
            )}

            {/* スナックバー */}
            {/* visible：表示／非表示を制御、onDismiss：非表示にする時の関数、action：アクションボタン、duration：表示時間（ミリ秒） */}
            <Snackbar
                visible={snackbarVisible}
                onDismiss={() => setSnackbarVisible(!snackbarVisible)}
                action={{
                    label: '閉じる',
                    onPress: () => setSnackbarVisible(!snackbarVisible)
                }}
                duration={10000}
            >
                エラーや正常終了などのメッセージ表示に使います
            </Snackbar>
        </View>
    )



}

const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    loadingContainer: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 16
    },
    loadingText: {
        marginVertical: 16
    },
    scrollView: {
        flex: 1,
        padding: 16
    },
    section: {
        marginBottom: 16
    },
    sectionTitle: {
        marginBottom: 8
    },
    divider: {
        marginVertical: 16
    },
    row: {
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
        paddingVertical: 8
    },
    radioContainer: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        marginBottom: 16
    },
    radioRow: {
        flexDirection: 'row',
        alignItems: 'center',
        width: '40%'
    },
    button: {
        marginVertical: 8
    }

})

export default PaperUISample
```

### 実行結果（Androidエミュレータ）
:::note info
当方の開発環境では、デフォルトカラー黄色で定義しているため、
全般的に黄色メインで画面表示されます。
（デフォルトは紫色になっております。）
:::
#### 初期表示
![スクリーンショット 2025-04-22 18.19.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/4eac0b81-73b9-4ccb-93b9-264559612ef7.png)
#### 画面入力後
![スクリーンショット 2025-04-22 18.20.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/bd4accc3-f595-4d78-86cb-bcb1e48597c5.png)
#### ローディング
![スクリーンショット 2025-04-22 18.22.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/b2227e2e-ff42-4957-bee6-2f60c7dd0adb.png)
#### バナー表示
![スクリーンショット 2025-04-22 18.23.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/0384b21e-c6ec-4158-ab0c-651083525331.png)
#### スナックバー表示
![スクリーンショット 2025-04-22 18.24.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/d58c0f9e-5d43-4bd2-8757-f9855c28db55.png)
ダイアログやモーダルなどのコンポーネントもありますので、
また時間があれば追記していきたいと思います。