Modal w React Native tutorial


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:

Joanna

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *