In many cases rendering and unrendering elements is troublesome as it usually requires storing values in state and toggling visible and hidden with setState
. Then because you need to delay the animations until the elements are rendered you have to pass in a componentDidUpdate
callback.
When rendering more views won't cause performance it may make more sense to render items hidden, and use the pointerEvents
View prop to have touches pass ignored.
In animations of this sort we would provide an opacity
animated style and set it to 0
. Then using a variable on state we would be toggling between none
and all
. When the view is hidden we pass in <View pointerEvents="none">
.
When we trigger our animation to show the view we can do our animation, and once it's complete using our start
callback we can toggle to <View pointerEvents="auto">
. Our view can now receive touch events.
Lets take a look at an example.
We'll start with a box. That box can be pressed to trigger an animation. The background color of the box will change every time the box is pressed.
import React, { Component } from "react"; import { AppRegistry, StyleSheet, Text, View, Animated, TouchableWithoutFeedback, TouchableOpacity, } from "react-native"; export default class animations extends Component { state = { animation: new Animated.Value(0), toggle: true, }; _pressed = false; handlePress = () => { const toValue = this._pressed ? 0 : 1; Animated.timing(this.state.animation, { toValue, }).start(); this._pressed = !this._pressed; }; render() { const boxStyle = { backgroundColor: this.state.animation.interpolate({ inputRange: [0, 1], outputRange: ["rgb(255,99,71)", "rgb(99,71,255)"], }), }; return ( <View style={styles.container}> <View> <TouchableWithoutFeedback onPress={this.handlePress}> <Animated.View style={[styles.box, boxStyle]} /> </TouchableWithoutFeedback> </View> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: "center", justifyContent: "center", }, box: { width: 100, height: 100, }, cover: { backgroundColor: "transparent", }, });
Now lets add in a little more. Lets add a toggle to toggle a variable.
handleToggle = () => { this.setState((state) => ({ toggle: !state.toggle, })); };
Now our view looks like this. A button will toggle our toggle
on state between true and false.
return ( <View style={styles.container}> <View> <TouchableWithoutFeedback onPress={this.handlePress}> <Animated.View style={[styles.box, boxStyle]} /> </TouchableWithoutFeedback> </View> <TouchableOpacity onPress={this.handleToggle}> <View> <Text>Toggle Pointer Events</Text> </View> </TouchableOpacity> </View> );
Now we add in a covering view and toggle our pointerEvents
. When this.state.toggle
is true
as it is in the start. There are no pointerEvents
on our covering view. This means you can press through it and trigger the animation.
However as soon as we toggle to false
the auto
pointerEvents
is applied. This means our covering view will accept touch events, blocking all touch events from getting to anything underneath it. You will no longer be able to change the color until you toggle back to true
.
return ( <View style={styles.container}> <View> <TouchableWithoutFeedback onPress={this.handlePress}> <Animated.View style={[styles.box, boxStyle]} /> </TouchableWithoutFeedback> <View style={[StyleSheet.absoluteFill, styles.cover]} pointerEvents={this.state.toggle ? "none" : "auto"} /> </View> <TouchableOpacity onPress={this.handleToggle}> <View> <Text>Toggle Pointer Events</Text> </View> </TouchableOpacity> </View> );
This can allow you to hide elements in plain site, animate them as necessary and then enable touch events on whichever elements need to receive touches.