Toast will be familiar to Android developers as it displays a prompt that is automatically hidden. RN provides an API for ToastAndroid. It can only be used on Android, but it has no effect on iOS. So, We need to adapt or customize one. Today’s article is to customize a Toast so that it can run on both Android and iOS and have the same operation effect.

Source code portal

Define the components

import React, {Component} from 'react';
import {
    StyleSheet,
    View,
    Easing,
    Dimensions,
    Text,
    Animated
} from 'react-native';
import PropTypes from 'prop-types';
import Toast from "./index";

const {width, height} = Dimensions.get("window"); const viewHeight = 35; class ToastView extends Component { static propTypes = { message:PropTypes.string, }; dismissHandler = null; constructor(props) { super(props); this.state = { message: props.message ! == undefined ? props.message :' '}}render() {
        return (
            <View style={styles.container} pointerEvents='none'>
                <Animated.View style={[styles.textContainer]}><Text
                    style={styles.defaultText}>{this.state.message}</Text></Animated.View>
            </View>
        )
    }
    componentDidMount() {
       this.timingDismiss()
    }

    componentWillUnmount() {
        clearTimeout(this.dismissHandler)
    }


    timingDismiss = () => {
        this.dismissHandler = setTimeout(() => {
            this.onDismiss()
        }, 1000)
    };

    onDismiss = () => {
        if (this.props.onDismiss) {
            this.props.onDismiss()
        }
    }
}

const styles = StyleSheet.create({
    textContainer: {
        backgroundColor: 'rgba(0,0,0,.6)',
        borderRadius: 8,
        padding: 10,
        bottom:height/8,
        maxWidth: width / 2,
        alignSelf: "flex-end",
    },
    defaultText: {
        color: "#FFF",
        fontSize: 15,
    },
    container: {
        position: "absolute",
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        flexDirection: "row",
        justifyContent: "center",}});export default ToastView
Copy the code

We’ll start by importing the basic components we need and the API that our custom components need to inherit, Dimensions to get the screen size, Easing to set the trajectory of the animation, and PropTypes to define the property types.

The Render method is the entry point where we define the component rendering. The outermost view uses position to absolute and sets left,right,top, and bottom to 0 to fill the screen so that the interface does not listen for click events during the Toast display. The inner View is the black box container displayed by Toast. The backgroundColor property is set to RGBA form and the color is black and the transparency is 0.6. And set the rounded corners and maximum width to half the screen width. Then there is the Text component to display the specific prompt.

We also see that propTypes is used to qualify the property message to be of type string. Constructor is the constructor of our component, which has a props argument, which is some properties passed in. Note that the constructor calls super(props) first, otherwise an error is reported, and here I set the value passed to state.

For Toast, the display automatically disappears after a while. We can do this with setTimeout, which is called on componentDidMount, where the time is set to 1000ms. Then expose the hidden destruction. When using setTimeout we also need to clear the timer when the component is uninstalled. ComponentWillUnmount when the component is unmounted. So clear the timer here.

Above, we implemented the Toast effect, but the show and hide were not overly animated and slightly stilted. So let’s animate some pan and transparency, and then modify componentDidMount to animate it and add two variables to the component

    moveAnim = new Animated.Value(height / 12);
    opacityAnim = new Animated.Value(0);
Copy the code

In the previous inner view style, the bottom was set to height/8. We set the View style here as follows

style={[styles.textContainer, {bottom: this.moveAnim, opacity: this.opacityAnim}]}
Copy the code

Then modify componentDidMount

 componentDidMount() {
        Animated.timing(
            this.moveAnim,
            {
                toValue: height / 8,
                duration: 80,
                easing: Easing.ease
            },
        ).start(this.timingDismiss);
        Animated.timing(
            this.opacityAnim,
            {
                toValue: 1,
                duration: 100,
                easing: Easing.linear
            },
        ).start();
    }
Copy the code

The bottom display moves from height/12 to height/8 in 80ms, and the transparency changes from 0 to 1 in 100ms. Above we can see that there is a easing property that transmits the curve speed of the animation. It can be implemented by itself and there are already a number of different effects in the Easing API. You can look to the implementation, the source address is https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/Easing.js, To achieve their own directly to a calculation function can be, you can go to see the imitation. In the previous section, we set Toast display time to 1000ms. We customize the display time and limit the type number.

        time: PropTypes.number
Copy the code

The handling of time in a constructor

            time: props.time && props.time < 1500 ? Toast.SHORT : Toast.LONG,
Copy the code

Here I’ve treated the time display to both SHORT and LONG, but you can make it whatever you want. Then just change the time 1000 in timingDismiss to this.state.time. When the component is updated again while it already exists, we need to deal with this by updating message and time in state, clearing the timer, and re-timing it.

componentWillReceiveProps(nextProps) { this.setState({ message: nextProps.message ! == undefined ? nextProps.message :' ',
            time: nextProps.time && nextProps.time < 1500 ? Toast.SHORT : Toast.LONG,
        })
        clearTimeout(this.dismissHandler)
        this.timingDismiss()
    }
Copy the code

The component registration

For our defined component to be called as an API, rather than written in the Render method, we define a trailing component

import React, {Component} from "react";
import {StyleSheet, AppRegistry, View, Text} from 'react-native';

viewRoot = null;

class RootView extends Component {
    constructor(props) {
        super(props);
        console.log("constructor:setToast")
        viewRoot = this;
        this.state = {
            view: null,
        }
    }

    render() {
        console.log("RootView");
        return (<View style={styles.rootView} pointerEvents="box-none">
            {this.state.view}
        </View>)
    }

    static setView = (View) => {this.setState viewroot.setState ({View: View})}; } const originRegister = AppRegistry.registerComponent; AppRegistry.registerComponent = (appKey, component) => {return originRegister(appKey, function () {
        const OriginAppComponent = component();

        return class extends Component {

            render() {
                return (
                    <View style={styles.container}>
                        <OriginAppComponent/>
                        <RootView/>
                    </View>
                );
            };
        };
    });
};
const styles = StyleSheet.create({
    container: {
        flex: 1,
        position: 'relative',
    },
    rootView: {
        position: "absolute",
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        flexDirection: "row",
        justifyContent: "center",}});export default RootView

Copy the code

RootView is us to define the root component, the realization of above, by AppRegistry. RegisterComponent registered. Wrap for external calls

import React, {
    Component,
} from 'react';
import RootView from '.. /RootView'
import ToastView from './ToastView'


class Toast {
    static LONG = 2000;
    static SHORT = 1000;

    static show(msg) {
        RootView.setView(<ToastView
            message={msg}
            onDismiss={() => {
                RootView.setView()
            }}/>)
    }

    static show(msg, time) {
        RootView.setView(<ToastView
            message={msg}
            time={time}
            onDismiss={() => {
                RootView.setView()
            }}/>)
    }
}

export default Toast

Copy the code

Two static variables are defined in Toast to represent the displayed time for external use. We then provide two static methods that call RootView’s setView method to set ToastView to the RootView.

First import the above Toast and then call it using the following method

                    Toast.show("Test, I'm Toast."); // set toast. show("Test",Toast.LONG);
Copy the code

All right, that’s it. If you want to see the full code, you can check it out on GitHub. Welcome to point out any inadequacies or mistakes in the article. And finally happy New Year.