こんにちは!かたつむり(@Katatumuri_nyan)です!
Reactを触ってみて、サイト的なものは作れるようになりました(*´ω`)
そこで、次はReactNativeを触ってみようと思い、簡単なテキストエディタを作成しようと企んでおります(笑)
今回は、メニューエリアを作成していきます!
機能を沢山付けるメニューエリアが一番大変なので、頑張りますw
GitHubでソースコードを管理しています!
最初から見る↓
【1】React Nativeでテキストエディタを作ってみる!【下調べ編】
前回を見る↓
【10】React Nativeでテキストエディタを作ってみる!【ナビ・エディタエリア作成編】
目次
メニューエリアを作成
↑この左側に表示されるメニューエリアを作っていきます!
デザイン段階では、テキストエリアの上に被るように表示する予定でした。
しかし今回は、利便性を考えて、テキストエリアと並ぶように表示したいと思います!
Menu.js
の作成
↑こんな感じで左側にメニューエリアを作成しました。
コードは↓このようになりました。
import React, { useContext } from 'react'; import { View, Text} from 'react-native'; import { useTheme } from 'react-native-elements'; import { ContextObject } from '../../modules/context'; export default function Menu() { const { theme } = useTheme(); const { } = useContext(ContextObject) const styles = { menu:{ width: 280, backgroundColor: theme.main.secondBackgroundColor, borderRadius: 20, marginRight: 10, padding:10, paddingTop: 20, flexDirection: 'column' }, title:{ color: theme.menu.titleColor, textAlign: 'center' } } return ( <View style={styles.menu}> <Text style={styles.title}>設定</Text> </View> ) }
メニューエリア開閉機能の作成
左から右にスワイプすることで、メニューエリアを開閉させたいと思います(*´ω`)
// Main.js <PanGestureHandler onGestureEvent={(event) => { onSwipeEvent(event) }}> <View style={styles.wrap}> {isMenuOpen ? <Menu />:<View/>} <EditorArea/> </View> </PanGestureHandler>
Main.js
で↑このようにラップしました。
// Main.js if (lefghtArea && swipeX < 0) { // (←)画面左半分を右から左にスワイプした時 setIsMenuOpen(false) setAbsoluteX(absoluteX) } else if (lefghtArea && swipeX > 0) { // (→)画面左半分を左から右にスワイプした時 setIsMenuOpen(true) setAbsoluteX(absoluteX) }
また、↑のように設定して、開閉できるようになりました(^▽^)/
あと、メニューエリアが開いていると、プレビューエリアの大きさが変わるので、スワイプ判定を変えました!
メニューボタンの作成
↑このようにボタンを作成しました(*´ω`)
色々機能をつけるので、とりあえずシンプルなボタンにしました。
// MenuBtn.js import React, { useContext } from 'react'; import { View ,Text} from 'react-native'; import { useTheme } from 'react-native-elements'; import { Icon } from 'react-native-elements'; import { ContextObject } from '../../../modules/context'; export default function MenuBtn() { const { theme } = useTheme(); const { } = useContext(ContextObject) const styles={ wrap: { width: '100%', height: 46, marginTop: 10, backgroundColor: theme.menuBtn.BackgroundColor, borderRadius: 20, flexDirection: 'row', alignItems: 'center', paddingLeft:10, paddingRight: 10 }, icon:{ color: theme.menuBtn.iconColor, marginRight: 10, fontSize: 28 }, btnText:{ color: theme.menuBtn.TextColor, fontSize: 18 } } return ( <View style={styles.wrap}> <Icon name='settings' iconStyle={styles.icon} /> <Text style={styles.btnText}>ボタン</Text> </View> ) }
// MenuBtnChild.js import React, { useContext } from 'react'; import { View ,Text} from 'react-native'; import { useTheme } from 'react-native-elements'; import { Icon } from 'react-native-elements'; import { ContextObject } from '../../../modules/context'; export default function MenuBtnChild() { const { theme } = useTheme(); const { } = useContext(ContextObject) const styles={ wrap: { alignSelf: 'flex-end', width: '90%', height: 46, marginTop: 10, backgroundColor: theme.menuBtnChild.BackgroundColor, borderColor: theme.menuBtnChild.BoderColor, borderStyle: 'solid', borderWidth:3, borderRadius: 20, flexDirection: 'row', alignItems: 'center', paddingLeft: 10, paddingRight: 10 }, icon:{ color: theme.menuBtn.iconColor, marginRight: 10, fontSize: 28 }, btnText:{ color: theme.menuBtn.TextColor, fontSize: 18 } } return ( <View style={styles.wrap}> <Icon name='settings' iconStyle={styles.icon} /> <Text style={styles.btnText}>ボタン</Text> </View> ) }
ナビの開閉の連動
↑こんな感じで、メニューが開いたとき、ナビも開いてほしいので、連動させます。
Nav.js
で以下のようにしました。
{isNavOpen | isMenuOpen? <NavOpened color={theme.nav.iconColor} />: <NavClosed color={theme.nav.iconColor} onPress={onNavOpen} /> }
ナビボタンを押したときの動作設定
↑このナビボタンを押したときに、メニューが開いてほしいので、機能を付けていきます!
// Nav.js function onPress(icon){ setWhichMenuOpen(icon); }
↑この様な関数を作成し、context.js
でデータのやりとりをすることにしました。
押したボタンによるコンポーネントのきりかえ
// Menu.js function WhichMenu(params) { const { theme } = useTheme(); const { whichMenuOpen } = useContext(ContextObject) switch (whichMenuOpen) { case 'settings': return <Settings /> break; case 'folder': return <Folder /> break; case 'file-upload': return <FileUpload /> break; default: return <Settings /> break; } }
↑このように、whichMenuOpen
の値によって表示するコンポーネントを変えます。
このあたりの挙動は、もうちょっと変更したい部分があるので、後で変更予定です!
メニュー内のボタンを押したときの挙動追加
設定メニュー
↑左のように表示したいのですが、ボタンを押すと右のように下に開くようにしたいです。
これを設定していきます!
開くメニュー😊 pic.twitter.com/p9WZfH8znR
— Katatumuri (@Katatumuri_nyan) July 8, 2021
↑このようにできました!
import React, { useContext, useState } from 'react'; import { View, Text} from 'react-native'; import { useTheme } from 'react-native-elements'; import { ContextObject } from '../../../modules/context'; import MenuBtn from '../_components/MenuBtn'; import MenuBtnChild from '../_components/MenuBtnChild'; import MenuTitle from '../_components/MenuTitle'; export default function Settings(props) { const { theme } = useTheme(); const { } = useContext(ContextObject) const [isThemeMenuBtnOpen, setThemeMenuBtnOpen]=useState(false) const [isPreviewMenuBtnOpen, setPreviewMenuBtnOpen] = useState(false) const [isAutoSaveMenuBtnOpen, setAutoSaveMenuBtnOpen] = useState(false) const styles = { menu: { }, } function onPress(is, set) { { is ? set(false) : set(true) } } return ( <View style={styles.menu}> <MenuTitle>設定</MenuTitle> <MenuBtn name='テーマ' onPress={() => { onPress(isThemeMenuBtnOpen, setThemeMenuBtnOpen) }} /> {isThemeMenuBtnOpen?<MenuBtnChild name='test'/>:<View/>} <MenuBtn name='プレビュー' onPress={() => { onPress(isPreviewMenuBtnOpen, setPreviewMenuBtnOpen) }} /> {isPreviewMenuBtnOpen ? <MenuBtnChild name='てす' /> : <View />} </View> ) }
↑コードはこのようになりました。
MenuBtn
とMenuBtnChild
をコンポーネントとしてまとめようか悩みましたが、ちょっと保留です。
とりあえず、設定メニューでスイッチコンポーネントなども使うので、まとめないことにします。
エクスポートメニュー
↑このようなかんじになります!
エクスポートメニューでは、開くようにはせず、押すだけのボタンにします。
import React, { useContext, useEffect } from 'react'; import { View, Text } from 'react-native'; import { ContextObject } from '../../../modules/context'; import MenuBtn from '../_components/MenuBtn'; import MenuTitle from '../_components/MenuTitle'; export default function Export() { const { setIsMenuOpen } = useContext(ContextObject) function onPress() { setIsMenuOpen(false) } return ( <View> <MenuTitle>エクスポート</MenuTitle> <MenuBtn name='Markdown' onPress={onPress} /> <MenuBtn name='HTML' onPress={onPress} /> <MenuBtn name='印刷' onPress={onPress} /> <MenuBtn name='バックアップ' onPress={onPress} /> </View> ) }
↑コードはこんな感じになりました。
今はonPress
に仮の関数を渡していますが、後でエクスポート用の関数に変えます!
フォルダーメニュー
↑フォルダーメニューを作成します。
ボタンを押すと開くのは設定メニューと同じですが、ファイルを選択した時などの挙動が変わるので、ちょっと難しいです。
↑のようになりました。
// Folder.js export default function Folder(params) { const { theme } = useTheme(); const { setIsMenuOpen } = useContext(ContextObject) const [isTypeSelectMenuOpen, setTypeSelectMenuOpen]=useState(false) const styles = { view:{ position: 'relative', zIndex:10 }, plusIconContainer: { position: 'absolute', top: -10, left: 0, zIndex:100, }, plusIcon:{ fontSize: 40 } } function onPressPlusIcon() { { isTypeSelectMenuOpen ? setTypeSelectMenuOpen(false):setTypeSelectMenuOpen(true)} } return ( <View style={styles.view}> <Icon name='add-circle' color={theme.PlusBtn.iconColor} containerStyle={styles.plusIconContainer} iconStyle={styles.plusIcon} onPress={onPressPlusIcon} /> {isTypeSelectMenuOpen ? <TypeSelectMenu /> : <View/>} <MenuTitle>プロジェクト</MenuTitle> <Project project={{ projectName:'test', fileList:['test','test2'] }} /> </View> ) }
// Folder.js function Project(props) { const { theme } = useTheme(); const [isOnonPressMenuBtn, setOnonPressMenuBtn] = useState(false) const projects = props.project const projectName = projects.projectName const fileList = projects.fileList const styles={ nodata:{ color: '#FFFFFF', marginTop: 30, }, } function onPressMenuBtn(params) { { isOnonPressMenuBtn ? setOnonPressMenuBtn(false) : setOnonPressMenuBtn(true)} } return( <View> {!projectName ? <Text style={styles.nodata}>+ボタンから新規作成</Text>: <MenuBtn name={projectName} iconName={isOnonPressMenuBtn ?'folder-open':'folder'} onPress={onPressMenuBtn} /> } {isOnonPressMenuBtn? ( !fileList ? <Text style={styles.nodata}>+ボタンから新規作成</Text> : (fileList.map(e=>{ return( <MenuBtnChild name={e} iconName='text-snippet' /> ) })) ): <View/>} </View> ) }
↑コードはコチラ
ファイルシステムを使って情報を取得するので、データはダミーです!
フォルダー・ファイル追加メニュー
↑このように、新規ディレクトリ・ファイルを作成できるようにします。
とりあえず、ボタンを作成しておきます。
↑このようになりました。
// Folder.js function TypeSelectMenuBtn(props) { const { theme } = useTheme(); const [isOnPress, setOnPress] = useState(false) console.log(isOnPress); const styles = { view: { backgroundColor: isOnPress ? theme.typeSelectMenu.BackgroundColor : theme.typeSelectMenu.onPress.BackgroundColor , height: '50%', flexDirection: 'row', alignItems: 'center', borderTopLeftRadius: props.addType == 'addProject'? 20 : 0, borderTopEndRadius: props.addType == 'addProject' ? 20 : 0, borderTopRightRadius: props.addType == 'addProject' ? 20 : 0, borderTopStartRadius: props.addType == 'addProject' ? 20 : 0, borderBottomLeftRadius: props.addType == 'addProject' ? 0 : 20, borderBottomEndRadius: props.addType == 'addProject' ? 0 : 20, borderBottomRightRadius: props.addType == 'addProject' ? 0 : 20, borderBottomStartRadius: props.addType == 'addProject' ? 0 : 20, padding:20, // borderBottomWidth:3 }, icon:{ color: isOnPress? theme.typeSelectMenu.iconColor : theme.typeSelectMenu.onPress.iconColor, marginRight: 10, fontSize: 30 }, text:{ color: isOnPress? theme.typeSelectMenu.TextColor : theme.typeSelectMenu.onPress.TextColor, fontSize: 20 } } function onPress() { props.onPress() { isOnPress ? setOnPress(false) : setOnPress(true) } console.log('onpress'); } return ( <Pressable style={styles.view} onPress={onPress}> <Icon name={props.iconName} iconStyle={styles.icon} /> <Text style={styles.text}>{props.text}</Text> </Pressable> ) }
↑コードはこんな感じ。
プロジェクト・ファイル追加用のモーダル作成
デザインでは書いていなかったですが、プロジェクトやファイルを追加する際にファイル名を入力しないといけないことに気づきました。
画面いっぱいのモーダルを作って、そこにインプットエリアを追加することにします。
Modal.js
を作っていきます!
↑できました!
import React, { useContext, useEffect, useState } from 'react'; import { Modal, TextInput, View, Pressable,Text} from 'react-native'; import { useTheme } from 'react-native-elements'; import { ContextObject } from '../../modules/context'; export function SetDataNameModal(props) { const { theme } = useTheme(); const { isSetDataNameModalOpen, setSetDataNameModalOpen, whichSetDataNameModalOpen, newProjectName, setNewProjectName, newFileName, setNewFileName } = useContext(ContextObject) const styles = { centeredView: { flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: 'rgba(255, 255, 255,0.5)', paddingBottom: props.keyboardPadding }, modal: { flexDirection: 'row', justifyContent: "center", alignItems: "center", backgroundColor: theme.main.mainBackgroundColor, textAlign: 'center', width: '80%', padding:20, borderRadius: 20 }, textInput: { width: '80%', height: 50, backgroundColor: theme.textView.backgroundColor, color: theme.textView.textColor, margin: 20, paddingHorizontal: 20, borderRadius: 20, fontSize: 20, }, btn: { height: 50, justifyContent: 'center', alignItems: 'center', backgroundColor: theme.main.secondBackgroundColor, borderRadius: 20, paddingHorizontal:20, }, btnText: { color: theme.nav.iconColor, fontSize: 20, } } function onChangeText(text) { if (whichSetDataNameModalOpen == 'addProject'){ setNewProjectName(text) } else if (whichSetDataNameModalOpen == 'addFile') { setNewFileName(text) } } function openModal(params) { setSetDataNameModalOpen(true) } function closeModal(params) { setSetDataNameModalOpen(false) } return ( <Modal transparent={true} presentationStyle='overFullScreen' onRequestClose={closeModal} visible={isSetDataNameModalOpen} animationType='fade' > <Pressable style={styles.centeredView} onPress={closeModal}> <Pressable style={styles.modal} onPress={openModal}> <TextInput style={styles.textInput} onChangeText={text => { onChangeText(text)}} placeholder={whichSetDataNameModalOpen == 'addProject'?'新規プロジェクト名':'新規ファイル名'} /> <Pressable style={styles.btn} onPress={closeModal} > <Text style={styles.btnText}>新規作成</Text> </Pressable> </Pressable> </Pressable> </Modal> ) }
↑コードはこんな感じ
押したボタンによって動作が変わるように設定しています。
ここから、難関のファイルシステムが始まります(((o(゚▽゚)o)))
がんばります!ww
↓続き
【12】React Nativeでテキストエディタを作ってみる!【ファイルシステム編】