10分で読めるReact Nativeの全公式コンポーネントまとめ
この記事はReact Native 全部俺 Advent Calendar 14日目の記事です。
このアドベントカレンダーについて
このアドベントカレンダーは @itome が全て書いています。
基本的にReact NativeおよびExpoの公式ドキュメントとソースコードを参照しながら書いていきます。誤植や編集依頼はXにお願いします。
10分で読めるReact Nativeの全公式コンポーネントまとめ
React Nativeには意外と少ない数の公式コンポーネントしかありません。その分1つ1つのコンポーネントがよく考えられていて、必要十分な機能を持っています。今回は各コンポーネントの機能と使い方について詳しく解説していきます。
Core Components
View
最も基本的なUIコンポーネントで、iOSのUIView
、Androidのandroid.view.View
、そしてWebでいうdiv
タグに相当します。レイアウトの作成やスタイリングのためのコンテナとして使用します。
// Viewの基本的な使い方
<View style={{ padding: 20, backgroundColor: 'white' }}>
<Text>これはViewの中身です</Text>
</View>
// flexboxを使った中央寄せレイアウト
<View style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5'
}}>
<Text>中央に配置されたテキスト</Text>
</View>
// 影をつける(iOSのみ)
<View style={{
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5, // Androidの場合はelevationで影をつける
}}>
<Text>影のついたカード</Text>
</View>
Text
テキストを表示するためのコンポーネントです。React Nativeではテキストは必ずTextコンポーネントの中に入れる必要があります。
// 基本的な使い方
<Text style={{ fontSize: 16, color: 'black' }}>
これはTextコンポーネントです
</Text>
// テキストのネスト
<Text style={{ fontSize: 16 }}>
この文章は
<Text style={{ fontWeight: 'bold' }}>
太字の部分
</Text>
を含みます
</Text>
// 行数制限とellipsisMode
<Text
numberOfLines={2}
ellipsisMode="tail"
style={{ fontSize: 14 }}
>
この文章は長いので2行に制限され、それを超える部分は...で省略されます。
この部分は表示されません。
</Text>
// 様々なスタイリング
<Text style={{
fontSize: 18,
fontWeight: '600',
color: '#333',
textAlign: 'center',
letterSpacing: 0.5,
lineHeight: 24,
textDecorationLine: 'underline',
fontStyle: 'italic'
}}>
スタイリングされたテキスト
</Text>
// onPressイベントの処理
<Text onPress={() => console.log('テキストがタップされました')}>
タップ可能なテキスト
</Text>
Image
画像を表示するためのコンポーネントです。ローカルの画像もネットワーク上の画像も表示できます。
画像のリサイズモードはresizeMode
プロパティで制御できます。cover
、contain
、stretch
、center
、repeat
の5つのモードがあります。
また、画像の読み込み状態をハンドリングするためのイベントも用意されています。
// ローカル画像の表示(サイズ指定は任意)
<Image
source={require('./logo.png')}
style={{ width: 100, height: 100 }}
/>
// ネットワーク画像の表示
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: 100, height: 100 }}
/>
// 画像のリサイズモード
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: 200, height: 200 }}
resizeMode="cover" // 画像が歪まないように、はみ出る部分をクリップ
/>
// 読み込み状態のハンドリング
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: 200, height: 200 }}
onLoadStart={() => console.log('読み込み開始')}
onProgress={(e) => console.log(`読み込み中: ${e.nativeEvent.loaded / e.nativeEvent.total}`)}
onLoad={() => console.log('読み込み完了')}
onError={(e) => console.log('読み込みエラー', e.nativeEvent.error)}
defaultSource={require('./placeholder.png')} // 読み込み中に表示する画像(iOSのみ)
/>
// 画像のキャッシュ制御
<Image
source={{
uri: 'https://example.com/image.jpg',
cache: 'reload' // キャッシュを無視して再読み込み
}}
style={{ width: 100, height: 100 }}
/>
// 背景色とボーダーを設定
<Image
source={require('./logo.png')}
style={{
width: 100,
height: 100,
backgroundColor: '#f5f5f5',
borderRadius: 50,
borderWidth: 2,
borderColor: '#ddd'
}}
/>
TextInput
テキスト入力のためのコンポーネントです。様々な入力タイプに対応しており、キーボードの種類やオートコレクト、セキュリティ入力など、多くの機能をカスタマイズできます。
const [text, setText] = useState('');
const [focused, setFocused] = useState(false);
// 基本的な使い方(制御コンポーネント)
<TextInput
value={text}
onChangeText={setText}
placeholder="ここに入力してください"
style={{
height: 40,
borderColor: focused ? 'blue' : 'gray',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10
}}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
/>
// パスワード入力
<TextInput
value={password}
onChangeText={setPassword}
secureTextEntry={true}
placeholder="パスワードを入力"
autoCapitalize="none" // 自動大文字化を無効化
autoCorrect={false} // オートコレクトを無効化
/>
// 複数行入力
<TextInput
multiline
numberOfLines={4}
value={text}
onChangeText={setText}
style={{
height: 100,
textAlignVertical: 'top', // Androidで上寄せにする
padding: 10,
borderWidth: 1
}}
/>
// キーボードタイプの指定
<TextInput
keyboardType="email-address" // メールアドレス用キーボード
autoCompleteType="email" // メールアドレスの自動補完
textContentType="emailAddress" // iOSのパスワード自動入力
returnKeyType="next" // キーボードの完了ボタンを"次へ"にする
onSubmitEditing={() => passwordInput.current?.focus()} // 次の入力欄にフォーカスを移動
/>
// 入力の検証
<TextInput
value={email}
onChangeText={text => {
setEmail(text);
setIsValid(validateEmail(text));
}}
style={{
borderColor: isValid ? 'green' : 'red',
borderWidth: 1
}}
onBlur={() => {
if (!isValid) {
Alert.alert('エラー', '正しいメールアドレスを入力してください');
}
}}
/>
ScrollView
スクロール可能なビューを作成するためのコンポーネントです。主に固定長のコンテンツをスクロールさせる場合に使用します。
ScrollViewは全てのコンテンツを一度にレンダリングするため、大量のデータを表示する場合はFlatListの使用を検討してください。
// 基本的な使い方
<ScrollView style={{ flex: 1 }}>
<View style={{ height: 1000 }}>
<Text>スクロールできる長いコンテンツ</Text>
</View>
</ScrollView>
// 横スクロール
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ padding: 10 }}
>
{[1, 2, 3, 4, 5].map(i => (
<View
key={i}
style={{
width: 100,
height: 100,
marginRight: 10,
backgroundColor: 'blue'
}}
/>
))}
</ScrollView>
// スクロール位置の制御
const scrollViewRef = useRef<ScrollView>(null);
<ScrollView
ref={scrollViewRef}
onContentSizeChange={() => {
// 常に最下部にスクロール
scrollViewRef.current?.scrollToEnd({ animated: true });
}}
onScroll={event => {
// スクロール位置の取得
console.log(event.nativeEvent.contentOffset.y);
}}
scrollEventThrottle={16} // スクロールイベントの間隔(ミリ秒)
>
{/* コンテンツ */}
</ScrollView>
// ページング機能(1画面ずつスクロール)
<ScrollView
horizontal
pagingEnabled
onMomentumScrollEnd={event => {
// 現在のページ番号を計算
const page = Math.round(
event.nativeEvent.contentOffset.x / Dimensions.get('window').width
);
console.log(`現在のページ: ${page}`);
}}
>
{[1, 2, 3].map(i => (
<View
key={i}
style={{
width: Dimensions.get('window').width,
height: '100%',
justifyContent: 'center',
alignItems: 'center'
}}
>
<Text>ページ {i}</Text>
</View>
))}
</ScrollView>
// ズーム機能
<ScrollView
maximumZoomScale={3}
minimumZoomScale={0.5}
bouncesZoom={true}
>
<Image
source={{ uri: 'https://example.com/large-image.jpg' }}
style={{ width: '100%', height: 500 }}
/>
</ScrollView>
FlatList
大量のデータをリスト表示する際に使用する最適化されたコンポーネントです。表示されている部分のみをレンダリングする(window化)ことで、パフォーマンスを向上させています。
また、リスト項目の再利用や、スクロール位置の保持、プルリフレッシュ、無限スクロールなど、リスト表示に必要な機能が一通り揃っています。
// 基本的な使い方
const data = Array.from({ length: 100 }, (_, i) => ({
id: String(i),
title: `項目 ${i}`
}));
<FlatList
data={data}
renderItem={({ item }) => (
<View style={{ padding: 20, borderBottomWidth: 1 }}>
<Text>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id}
/>
// ヘッダーとフッター
<FlatList
data={data}
ListHeaderComponent={() => (
<View style={{ padding: 20, backgroundColor: '#f5f5f5' }}>
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>リストのヘッダー</Text>
</View>
)}
ListFooterComponent={() => (
<View style={{ padding: 20, alignItems: 'center' }}>
<Text>リストの最後です</Text>
</View>
)}
ItemSeparatorComponent={() => (
<View style={{ height: 1, backgroundColor: '#eee' }} />
)}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
/>
// 無限スクロール
const [loading, setLoading] = useState(false);
const [data, setData] = useState<Item[]>([]);
<FlatList
data={data}
onEndReached={async () => {
if (loading) return;
setLoading(true);
const newData = await fetchMoreData();
setData([...data, ...newData]);
setLoading(false);
}}
onEndReachedThreshold={0.5} // 末尾から50%の位置で読み込み開始
ListFooterComponent={() => (
loading ? <ActivityIndicator /> : null
)}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
/>
// プルリフレッシュ
const [refreshing, setRefreshing] = useState(false);
<FlatList
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={async () => {
setRefreshing(true);
const newData = await refreshData();
setData(newData);
setRefreshing(false);
}}
/>
}
data={data}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
/>
// パフォーマンス最適化
<FlatList
data={data}
initialNumToRender={10} // 初期表示数
maxToRenderPerBatch={10} // 1バッチあたりのレンダリング数
windowSize={5} // ウィンドウサイズ(画面の前後何画面分をレンダリングするか)
removeClippedSubviews={true} // 画面外の要素をアンマウント
getItemLayout={(data, index) => ({
// 高さが固定の場合、位置計算を最適化
length: 50,
offset: 50 * index,
index,
})}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
/>
SectionList
FlatListにセクション(見出し)を追加できるバージョンです。設定項目やカテゴリー分けされたリストの表示に使用します。
データ構造が少し複雑になりますが、その分柔軟なリスト表示が可能です。また、FlatListと同様のパフォーマンス最適化機能も備えています。
// 基本的な使い方
const DATA = [
{
title: 'メイン設定',
data: ['プロフィール', 'アカウント', 'セキュリティ'],
},
{
title: '通知設定',
data: ['メール通知', 'プッシュ通知', 'アラート音'],
},
];
<SectionList
sections={DATA}
renderItem={({ item }) => (
<View style={{ padding: 15 }}>
<Text>{item}</Text>
</View>
)}
renderSectionHeader={({ section: { title } }) => (
<View style={{
padding: 10,
backgroundColor: '#f5f5f5',
borderBottomWidth: 1,
borderBottomColor: '#eee'
}}>
<Text style={{ fontWeight: 'bold' }}>{title}</Text>
</View>
)}
/>
// セクションフッターとリストヘッダー/フッター
<SectionList
sections={DATA}
renderSectionFooter={({ section: { data } }) => (
<View style={{ padding: 10, backgroundColor: '#f9f9f9' }}>
<Text style={{ color: 'gray' }}>
{data.length}個の項目
</Text>
</View>
)}
ListHeaderComponent={() => (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 24 }}>設定</Text>
</View>
)}
ListFooterComponent={() => (
<View style={{ padding: 20 }}>
<Text style={{ color: 'gray' }}>バージョン 1.0.0</Text>
</View>
)}
renderItem={({ item }) => (
<Text>{item}</Text>
)}
/>
// スティッキーヘッダー
<SectionList
sections={DATA}
stickySectionHeadersEnabled
renderSectionHeader={({ section: { title } }) => (
<View style={{
padding: 10,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee'
}}>
<Text style={{ fontWeight: 'bold' }}>{title}</Text>
</View>
)}
renderItem={({ item }) => (
<Text>{item}</Text>
)}
/>
// セクションごとの展開/折りたたみ
const [expandedSections, setExpandedSections] = useState(new Set<string>());
<SectionList
sections={DATA}
renderSectionHeader={({ section: { title } }) => (
<Pressable
onPress={() => {
setExpandedSections(prev => {
const next = new Set(prev);
if (next.has(title)) {
next.delete(title);
} else {
next.add(title);
}
return next;
});
}}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
padding: 10,
backgroundColor: '#f5f5f5'
}}
>
<Text style={{ fontWeight: 'bold' }}>{title}</Text>
<Text>{expandedSections.has(title) ? '▼' : '▶'}</Text>
</Pressable>
)}
renderItem={({ item, section: { title } }) => (
expandedSections.has(title) ? (
<View style={{ padding: 15 }}>
<Text>{item}</Text>
</View>
) : null
)}
/>
VirtualizedList
FlatListやSectionListの基となるコンポーネントです。独自のリスト表示コンポーネントを作成する場合に使用します。
通常は直接使用することは少ないですが、特殊なデータ構造を扱う場合やカスタムの最適化が必要な場合に便利です。
// 基本的な使い方
const data = Array.from({ length: 100 }, (_, i) => ({
id: String(i),
title: `項目 ${i}`
}));
<VirtualizedList
data={data}
initialNumToRender={4}
renderItem={({ item }: { item: typeof data[0] }) => (
<View style={{ padding: 20 }}>
<Text>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id}
getItemCount={data => data.length}
getItem={(data, index) => data[index]}
/>
// カスタムデータ構造
type TreeNode = {
id: string;
title: string;
children?: TreeNode[];
};
const flattenTree = (node: TreeNode): TreeNode[] => {
const result: TreeNode[] = [node];
if (node.children) {
node.children.forEach(child => {
result.push(...flattenTree(child));
});
}
return result;
};
const treeData = {
id: '1',
title: 'ルート',
children: [
{
id: '2',
title: '子ノード1',
children: [
{ id: '4', title: '孫ノード1' },
{ id: '5', title: '孫ノード2' }
]
},
{
id: '3',
title: '子ノード2'
}
]
};
const flattenedData = flattenTree(treeData);
<VirtualizedList
data={flattenedData}
renderItem={({ item }) => (
<View style={{
padding: 20,
paddingLeft: getNodeDepth(item.id) * 20
}}>
<Text>{item.title}</Text>
</View>
)}
getItemCount={data => data.length}
getItem={(data, index) => data[index]}
keyExtractor={item => item.id}
/>
// パフォーマンス最適化
<VirtualizedList
data={data}
initialNumToRender={10}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
windowSize={5}
removeClippedSubviews={Platform.OS === 'android'}
getItemLayout={(data, index) => ({
length: 50,
offset: 50 * index,
index,
})}
renderItem={({ item }) => (
<View style={{ height: 50 }}>
<Text>{item.title}</Text>
</View>
)}
getItemCount={data => data.length}
getItem={(data, index) => data[index]}
keyExtractor={item => item.id}
onEndReached={() => {
// 無限スクロール
loadMoreData();
}}
onEndReachedThreshold={0.5}
refreshing={isRefreshing}
onRefresh={() => {
// プルリフレッシュ
refreshData();
}}
/>
Pressable
より柔軟なインタラクションを実装するためのコンポーネントです。タップ、長押し、ホバーなど様々なイベントに対応でき、プレス状態に応じたスタイリングも可能です。
Button、TouchableOpacityなどの他のタッチャブルコンポーネントと比べて、より細かいカスタマイズが可能です。
// 基本的な使い方
<Pressable
onPress={() => console.log('押されました')}
style={({ pressed }) => [
{
backgroundColor: pressed ? '#ddd' : '#fff',
padding: 10,
borderRadius: 5
}
]}
>
<Text>タップしてください</Text>
</Pressable>
// 様々なインタラクション
<Pressable
onPressIn={() => console.log('タッチ開始')}
onPressOut={() => console.log('タッチ終了')}
onLongPress={() => console.log('長押し')}
delayLongPress={500} // 長押しと判定されるまでの時間(ミリ秒)
disabled={false} // タッチを無効化
hitSlop={20} // タッチ判定領域を広げる
style={({ pressed, focused, focused }) => [
{
opacity: pressed ? 0.5 : 1,
backgroundColor: focused ? 'yellow' : 'white',
borderWidth: focused ? 2 : 0
}
]}
>
<Text>インタラクティブなボタン</Text>
</Pressable>
// カスタムリップルエフェクト(Android)
<Pressable
android_ripple={{
color: 'rgba(0, 0, 0, 0.1)',
borderless: false,
radius: 20
}}
style={{
padding: 10,
backgroundColor: 'white',
borderRadius: 5
}}
>
<Text>Androidのリップルエフェクト</Text>
</Pressable>
// ネストされたPressable
<Pressable
onPress={() => console.log('外側')}
style={{ padding: 20, backgroundColor: '#f5f5f5' }}
>
<Text>外側のPressable</Text>
<Pressable
onPress={(e) => {
e.stopPropagation(); // 親要素へのイベント伝播を止める
console.log('内側');
}}
style={{ padding: 10, backgroundColor: '#ddd', marginTop: 10 }}
>
<Text>内側のPressable</Text>
</Pressable>
</Pressable>
Modal
画面全体に表示されるオーバーレイコンポーネントです。確認ダイアログやフルスクリーンのメニューなど、様々な用途に使用できます。
iOSとAndroidで少し異なる動作をするため、プラットフォーム固有の挙動に注意が必要です。
// 基本的な使い方
const [visible, setVisible] = useState(false);
<View style={{ flex: 1 }}>
<Button title="モーダルを開く" onPress={() => setVisible(true)} />
<Modal
visible={visible}
animationType="slide" // slide, fade, noneから選択
transparent={true}
onRequestClose={() => {
// Androidのバックボタン押下時の処理
setVisible(false);
}}
>
<View style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)'
}}>
<View style={{
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
elevation: 5
}}>
<Text>モーダルの内容</Text>
<Button title="閉じる" onPress={() => setVisible(false)} />
</View>
</View>
</Modal>
</View>
// ボトムシート
<Modal
visible={visible}
animationType="slide"
transparent={true}
>
<Pressable
style={{ flex: 1 }}
onPress={() => setVisible(false)}
>
<View style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'white',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: -2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
}}>
<View style={{
width: 40,
height: 4,
backgroundColor: '#ccc',
borderRadius: 2,
alignSelf: 'center',
marginBottom: 10
}} />
<Text>ボトムシートの内容</Text>
</View>
</Pressable>
</Modal>
// 画面の向きに対応
<Modal
visible={visible}
supportedOrientations={[
'portrait',
'portrait-upside-down',
'landscape',
'landscape-left',
'landscape-right'
]}
>
{/* モーダルの内容 */}
</Modal>
// ステータスバーの制御
<Modal
visible={visible}
statusBarTranslucent={true} // Androidでステータスバーを透過
>
<View style={{
flex: 1,
marginTop: Platform.OS === 'ios' ?
getStatusBarHeight() : 0 // iOSでステータスバーの高さを考慮
}}>
{/* モーダルの内容 */}
</View>
</Modal>
ActivityIndicator
ローディング状態を表示するためのシンプルなスピナーコンポーネントです。OSネイティブのデザインが使用されます。
// 基本的な使い方
<ActivityIndicator size="large" color="#0000ff" />
// サイズとカラーのカスタマイズ
<ActivityIndicator
size={Platform.OS === 'ios' ? 'large' : 50}
color="#00ff00"
/>
// ローディングオーバーレイ
const LoadingOverlay = ({ loading }: { loading: boolean }) => {
if (!loading) return null;
return (
<View style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center',
alignItems: 'center'
}}>
<View style={{
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center'
}}>
<ActivityIndicator style={{ marginRight: 10 }} />
<Text>読み込み中...</Text>
</View>
</View>
);
};
// プルリフレッシュとの組み合わせ
<FlatList
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#0000ff" // ActivityIndicatorの色(iOS)
colors={['#0000ff']} // ActivityIndicatorの色(Android)
/>
}
data={data}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
ListFooterComponent={() => (
loading ? (
<View style={{ padding: 20 }}>
<ActivityIndicator />
</View>
) : null
)}
/>
StatusBar
ステータスバー(画面上部の時刻やバッテリー残量を表示する領域)の見た目をカスタマイズするためのコンポーネントです。
// 基本的な使い方
<StatusBar
barStyle="dark-content" // ステータスバーのテキストカラー
backgroundColor="white" // 背景色(Androidのみ)
hidden={false} // ステータスバーの表示/非表示
/>
// プラットフォーム別の制御
<StatusBar
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'light-content'}
backgroundColor={Platform.OS === 'android' ? '#000' : undefined}
translucent={Platform.OS === 'android'} // ステータスバーを透過(Android)
/>
// アニメーションの制御(iOSのみ)
<StatusBar
animated={true}
hideTransitionAnimation="fade" // slide, fade, noneから選択
/>
// ネットワーク接続状態の表示(iOSのみ)
<StatusBar
networkActivityIndicatorVisible={isLoading}
/>
// コンテキストごとに切り替え
const Navigation = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerStatusBarHeight: Platform.OS === 'ios' ?
getStatusBarHeight() : undefined,
// ヘッダーにステータスバーの高さを追加
}}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{
headerTransparent: true,
// ヘッダーを透過させる場合はステータスバーも考慮
}}
/>
</Stack.Navigator>
);
};
KeyboardAvoidingView
キーボードが表示された時に、自動的にコンテンツの位置を調整するコンポーネントです。フォームの実装時に特に重要です。
// 基本的な使い方
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1 }}
>
<ScrollView>
<TextInput
placeholder="名前"
style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
/>
<TextInput
placeholder="メールアドレス"
style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
/>
<TextInput
placeholder="パスワード"
style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
/>
</ScrollView>
</KeyboardAvoidingView>
// モーダル内でのキーボード対応
<Modal visible={visible}>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={{ flex: 1 }}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<TextInput
style={{
padding: 10,
borderWidth: 1,
marginBottom: 10,
backgroundColor: 'white'
}}
/>
<Button title="送信" onPress={onSubmit} />
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
</Modal>
Platform Specific Components
プラットフォーム固有のコンポーネントについても解説していきます。
SafeAreaView (iOS)
iPhoneのノッチやパンチホールなどの領域を避けてコンテンツを表示するためのコンポーネントです。最近のデバイスではノッチが標準的なのでよく使うコンポーネントですが、Androidに対応していないため、 react-native-safe-area-context
などのサードパーティーライブラリを使う方がおすすめです。
// 基本的な使い方
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<Text>安全な領域に表示されるコンテンツ</Text>
</View>
</SafeAreaView>
// 部分的な適用
const Screen = () => (
<View style={{ flex: 1 }}>
<SafeAreaView>
<Header />
</SafeAreaView>
<ScrollView style={{ flex: 1 }}>
<Content />
</ScrollView>
<SafeAreaView>
<Footer />
</SafeAreaView>
</View>
);
// 色付きの背景
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
<StatusBar barStyle="dark-content" />
<View style={{ flex: 1 }}>
<Text>コンテンツ</Text>
</View>
</SafeAreaView>
DrawerLayoutAndroid (Android)
Android特有のドロワーメニューを実装するためのコンポーネントです。
// DrawerLayoutAndroidの続き
<DrawerLayoutAndroid
ref={drawerRef}
drawerWidth={300}
drawerPosition="left"
renderNavigationView={() => (
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<Text style={{ margin: 10, fontSize: 15 }}>
ドロワーメニュー
</Text>
<Button
title="閉じる"
onPress={() => drawerRef.current?.closeDrawer()}
/>
<View style={{ padding: 10 }}>
<Pressable
style={{ padding: 10 }}
onPress={() => {
navigation.navigate('Home');
drawerRef.current?.closeDrawer();
}}
>
<Text>ホーム</Text>
</Pressable>
<Pressable
style={{ padding: 10 }}
onPress={() => {
navigation.navigate('Settings');
drawerRef.current?.closeDrawer();
}}
>
<Text>設定</Text>
</Pressable>
</View>
</View>
)}
drawerBackgroundColor="#fff"
onDrawerOpen={() => console.log('ドロワーが開きました')}
onDrawerClose={() => console.log('ドロワーが閉じました')}
>
<View style={{ flex: 1 }}>
<Button
title="ドロワーを開く"
onPress={() => drawerRef.current?.openDrawer()}
/>
<Text>メインコンテンツ</Text>
</View>
</DrawerLayoutAndroid>
// ジェスチャーの制御
<DrawerLayoutAndroid
drawerLockMode="locked-closed" // ジェスチャーでの開閉を無効化
keyboardDismissMode="on-drag" // ドロワーを開くときにキーボードを閉じる
>
{/* コンテンツ */}
</DrawerLayoutAndroid>
// スライド時のアニメーション
<DrawerLayoutAndroid
drawerSlideAnimationStyle="slide" // fade, noneも選択可能
onDrawerSlide={(event) => {
// スライド量(0-1)に応じた処理
const offset = event.nativeEvent.offset;
// メインコンテンツのアニメーションなど
}}
>
{/* コンテンツ */}
</DrawerLayoutAndroid>
TouchableNativeFeedback (Android)
Android特有のリップルエフェクトを実装するためのコンポーネントです。Material Designのタッチフィードバックを実現できます。
// 基本的な使い方
<TouchableNativeFeedback
background={TouchableNativeFeedback.SelectableBackground()}
onPress={() => console.log('タップされました')}
>
<View style={{ padding: 10, backgroundColor: '#fff' }}>
<Text>タップするとリップルエフェクトが表示されます</Text>
</View>
</TouchableNativeFeedback>
// 様々な背景エフェクト
<TouchableNativeFeedback
background={TouchableNativeFeedback.Ripple('#2196F3', false)}
useForeground={true}
>
<View style={{
padding: 10,
backgroundColor: '#fff',
borderRadius: 5
}}>
<Text>カスタムカラーのリップル</Text>
</View>
</TouchableNativeFeedback>
// 境界のある/なしリップル
<TouchableNativeFeedback
background={TouchableNativeFeedback.Ripple('#2196F3', true)} // trueで境界なし
useForeground={true}
>
<View style={{ padding: 10 }}>
<Text>境界なしリップル</Text>
</View>
</TouchableNativeFeedback>
// APIレベルに応じた分岐
<TouchableNativeFeedback
background={
Platform.Version >= 21
? TouchableNativeFeedback.Ripple('#2196F3', false)
: TouchableNativeFeedback.SelectableBackground()
}
>
<View style={{ padding: 10 }}>
<Text>APIレベルに応じたエフェクト</Text>
</View>
</TouchableNativeFeedback>
InputAccessoryView (iOS)
iOS特有のキーボードアクセサリービュー(キーボードの上に表示されるツールバー)を実装するためのコンポーネントです。
// 基本的な使い方
const inputAccessoryViewID = 'uniqueID';
<View style={{ flex: 1 }}>
<TextInput
inputAccessoryViewID={inputAccessoryViewID}
// このIDを持つInputAccessoryViewがキーボード上に表示される
/>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<View style={{
backgroundColor: '#f1f1f1',
borderTopWidth: 1,
borderColor: '#ccc',
padding: 10,
flexDirection: 'row',
justifyContent: 'space-between'
}}>
<Button title="キャンセル" onPress={() => {}} />
<Button title="完了" onPress={() => {}} />
</View>
</InputAccessoryView>
</View>
// カスタムツールバー
const CustomInputAccessory = () => {
return (
<InputAccessoryView nativeID="customToolbar">
<View style={{
backgroundColor: '#f1f1f1',
padding: 10,
flexDirection: 'row',
justifyContent: 'space-around'
}}>
<Button title="B" onPress={() => {/* 太字 */}} />
<Button title="I" onPress={() => {/* イタリック */}} />
<Button title="U" onPress={() => {/* 下線 */}} />
<Button title="完了" onPress={Keyboard.dismiss} />
</View>
</InputAccessoryView>
);
};
// キーボードの高さに追従
const [keyboardHeight, setKeyboardHeight] = useState(0);
useEffect(() => {
const keyboardWillShow = Keyboard.addListener(
'keyboardWillShow',
e => setKeyboardHeight(e.endCoordinates.height)
);
const keyboardWillHide = Keyboard.addListener(
'keyboardWillHide',
() => setKeyboardHeight(0)
);
return () => {
keyboardWillShow.remove();
keyboardWillHide.remove();
};
}, []);
<InputAccessoryView nativeID="customToolbar">
<View style={{
backgroundColor: '#f1f1f1',
padding: 10,
marginBottom: keyboardHeight
}}>
{/* ツールバーの内容 */}
</View>
</InputAccessoryView>
まとめ
このように、React Nativeの公式コンポーネントには基本的なUIパーツが一通り揃っています。プラットフォーム固有のコンポーネントを使いこなすことで、より自然なアプリ体験を提供することができます。
実際のアプリ開発では、これらのコンポーネントを組み合わせて使用したり、サードパーティのライブラリで拡張したりすることになります。特に複雑なUIを実装する場合は、React Native ElementsやReact Native PaperなどのUIライブラリの利用を検討するとよいでしょう。
Discussion