Komponent Modal posiada wiele zastosowań. Bardzo często używany jest jako „okienko” , w którym można wykonać dodatkowe akcje, których rezultaty możemy oglądać na ekranie. Modal może służyć np. jako okienko logowania, okienko z informacją albo okienko z opcjami do wyboru.
Stworzymy zatem dwa rodzaje tego komponentu: Simple modal i modal picker.
Simple modal
Załóżmy, że chcemy, aby użytkownik potwierdził lub odrzucił swój wybór. Możemy zaoferować mu dwie opcje do wyboru: Cancel i Ok. Modal będzie wyglądał tak:
Nasz prosty modal zaczniemy od utworzenia dwóch plików: Modal.js i SimpleModal.js. W stanie komponentu Modal.js określimy, czy modal ma być widoczny czy nie. Zapiszemy tam również wybór użytkowanika naszej aplikacji:
import React, {Component} from 'react';
import {StyleSheet, Text, View, Modal, TouchableHighlight, } from 'react-native';
import SimpleModal from './SimpleModal';
export default class MyModal extends Component {
constructor(props) {
super(props);
this.state = {
isModalVisible: false,
choosenData: '',
};
}
render() {
return (....)
}
}
Stworzymy także przycisk (wykorzystując do tego TouchableHighlight), po naciśnięciu którego modal stanie się widoczny. Stworzymy też sam modal, który będzie transparentny. Treść komponentu Modal zaimportujemy z SimpleModal.js
render() {
return (
<View style={styles.contentContainer}>
<Text style={styles.text}>
{this.state.choosenData}
</Text>
<TouchableHighlight onPress={() => this.changeModalVisibility(true)} underlayColor={'#f1f1f1'}
style={[styles.touchableHighlight, {backgroundColor: 'orange'}]}>
<Text style={styles.text}>Open Modal</Text>
</TouchableHighlight>
<Modal transparent={true} animationType="fade" visible={this.state.isModalVisible}
onRequestClose={() => this.changeModalVisibility(false)} style={styles.modalContainer}>
<SimpleModal changeModalVisibility={this.changeModalVisibility} setData={this.setData} />
</Modal>
</View>
)
}
Dodamy odpowiednie style, aby całość była bardziej czytelna i przejrzysta.
const styles = StyleSheet.create({
contentContainer: {
flex: 1,
backgroundColor: 'yellow',
alignItems: 'center',
justifyContent: 'center',
},
text: {
marginVertical: 20,
fontSize: 20,
fontWeight: 'bold',
},
touchableHighlight: {
backgroundColor: 'white',
alignSelf: 'stretch',
alignItems: 'center',
},
modalContainer: {
alignItems: 'center',
justifyContent: 'center',
}
})
Napiszemy teraz funkcje, które kierować będą widocznością modal-a oraz zapisywać dokonany wybór w stanie komponentu:
changeModalVisibility = (bool) => {
this.setState({ isModalVisible: bool })
}
setData = (data) => {
this.setState({ choosenData: data })
}
Brakuje jeszcze wnętrza dla komponentu Modal. Chcemy, aby jego szerokość dopasowana była do szerokości ekranu lecz by jednocześnie po bokach posiadał marginesy. W tym celu dodamy event listener do reactowego komponentu Dimensions. Chcemy również zaoferować do wyboru dwie możliwości: zatwierdzenie i odrzucenie naszej sugestii. Stworzymy zatem dwa przyciski – Cancel i Ok. Dokonany wybór przekażemy do stanu komponentu-rodzica (patrz: jak przekazać dane z komponentu dziecka do komponentu rodzica), aby mógł wyświetlić go na ekranie. Jednocześnie dokonując jakiegokolwiek wyboru, zamykamy modal.
SimpleModal.js
import React, {Component} from 'react';
import {StyleSheet, Text, View, TouchableOpacity, TouchableHighlight, Dimensions } from 'react-native';
export default class SimpleModal extends Component {
constructor(props) {
super(props);
this.state = {
width: Dimensions.get('window').width,
};
Dimensions.addEventListener('change', (e) => {
this.setState(e.window);
});
}
closeModal = (bool, data) => {
this.props.changeModalVisibility(bool)
this.props.setData(data);
}
render() {
return (
<TouchableOpacity disabled={true} style={styles.contentContainer}>
<View style={[styles.modal, {width: this.state.width - 80}]}>
<View style={styles.textView}>
<Text style={[styles.text, {fontSize: 20}]}>Sample modal header</Text>
<Text style={styles.text}>Sample modal text</Text>
</View>
<View style={styles.buttonsView}>
<TouchableHighlight onPress={() => this.closeModal(false, 'Cancel')} style={styles.touchableHighlight} underlayColor={'#f1f1f1'} >
<Text style={[styles.text, {color: 'blue'}]}>
Cancel
</Text>
</TouchableHighlight>
<TouchableHighlight onPress={() => this.closeModal(false, 'Ok')} style={styles.touchableHighlight} underlayColor={'#f1f1f1'} >
<Text style={[styles.text, {color: 'blue'}]}>
Ok
</Text>
</TouchableHighlight>
</View>
</View>
</TouchableOpacity>
)
}
};
Zostały jeszcze style:
const styles = StyleSheet.create({
contentContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
modal: {
height: 150,
paddingTop: 10,
alignSelf: 'center',
alignItems: 'center',
textAlign: 'center',
backgroundColor: "white",
borderRadius: 10,
},
text: {
margin: 5,
fontSize: 16,
fontWeight: 'bold',
},
touchableHighlight: {
flex: 1,
backgroundColor: 'white',
paddingVertical: 10,
alignSelf: 'stretch',
alignItems: 'center',
borderRadius: 10,
},
textView: {
flex: 1,
alignItems: 'center',
},
buttonsView: {
width: '100%',
flexDirection: 'row',
}
})
Yay, działa!
React Native modal Picker with ScrollView
Kolej na modal z większą ilością opcji wyboru, które nie mieszczą się na ekranie. W stosunku do poprzedniego przykładu w Modal.js dokonać musimy nieznacznych zmian:
W stanie komponentu dodajemy opcję color, z której odczytywany będzie kolor przycisku otwierającego modal.
Modal.js
this.state = {
isModalVisible: false,
color: '',
};
W funkcji setData zmieniamy nazwę ustawianego paramertu:
setData = (data) => {
this.setState({ color: data })
}
Zmieniamy style dla przycisku otwierającego modal:
<TouchableHighlight onPress={() => this.changeModalVisibility(true)} underlayColor={'#f1f1f1'}
style={[styles.touchableHighlight, {backgroundColor: this.state.color!=='' ? (this.state.color) : ('orange') }]} >
<Text style={styles.text}>Open Modal</Text>
</TouchableHighlight>
Tworzymy nowy komponent o nazwie ModalPicker.js i tak jak poprzednio, dopasowujemy jego wymiary do wymiarów ekranu, tym razem zarówno szerokość, jak i wysokość. Tworzymy tablicę z danymi – w naszym przypadku są to kolory, i stosując metodę map() dla każdego elementu w tablicy zwracamy widok z nazwą koloru. Po dokonaniu jakiegokolwiek wyboru kolor czcionki wybranej opcji zmienia się zgodnie z jego nazwą. Zatwierdzając wybór przyciskiem Choose color przesyłamy do stanu komponentu-rodzica informację o wybranym kolorze i zamykamy modal. Teraz rodzic może wyświetlić przycisk w wybranym przez nas kolorze.
Kod komponentu ModalPicker.js prezentuje się następująco:
ModalPicker.js
import React, {Component} from 'react';
import {StyleSheet, Text, View, ScrollView, TouchableOpacity, TouchableHighlight, Dimensions } from 'react-native';
export default class ModalPicker extends Component {
constructor(props) {
super(props);
this.options = ['red', 'blue', 'yellow', 'maroon', 'orange', 'green', 'gray', 'black', 'purple', 'pink', 'brown', 'navy', 'fuchsia', 'silver' ]
this.state = {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
selectedItem: '',
};
Dimensions.addEventListener("change", (e) => {
this.setState(e.window);
});
}
changeModalVisibility = (bool) => {
this.setState({ isModalVisible: bool })
}
closeModal = (bool, data) => {
if (this.state.selectedItem === '') {
return
}
this.props.changeModalVisibility(bool)
this.props.setData(data);
this.setState({ selectedItem: '' })
}
render() {
const option = this.options.map( (option, index) => {
const borderBottom = index===this.options.length-1 ? (null) : (<View style={styles.borderBottom}></View>);
return <TouchableOpacity activeOpacity={1} style={styles.option} key={index}
onPress={() => this.setState({ selectedItem: option })}>
<Text style={ this.state.selectedItem===option ? ([styles.text, {color: option}]) : (styles.text) }>
{option.charAt(0).toUpperCase() + option.slice(1)}
</Text>
{borderBottom}
</TouchableOpacity>
})
return (
<TouchableOpacity activeOpacity={1} onPress={() => this.props.changeModalVisibility(false)} style={styles.contentContainer}>
<View style={[styles.modal, {width: this.state.width - 80, height: this.state.height - 80}]}>
<ScrollView style={styles.optionView}>
{option}
</ScrollView>
<TouchableHighlight onPress={() => this.closeModal(false, this.state.selectedItem)}
style={[styles.touchableHighlight, {backgroundColor: 'orange'}]} underlayColor={'#f1f1f1'}>
<Text style={styles.text}>
Choose color
</Text>
</TouchableHighlight>
</View>
</TouchableOpacity>
)
}
};
Nasz modal wygląda tak: