React Native Animation Basic - react-native

So I am practicing the React Native Reanimated or the Animation in React Native with Expo. I'm trying to open a screen from bottom to top using Animated.
When I press the Text it will show another screen on top of my Parent Component with Animation from bottom to top.
But this is the output I get:
Output I want to get:
Here are my codes:
class App extends React.Component {
state = {
openHome: false
}
animation = new Value(0);
openHome = () => {
this.setState({
openHome: true
})
Animated.timing(this.animation, {
duration: 300,
toValue: 1
}).start();
}
render () {
const {animation} = this;
const translateY = animation.interpolate({
inputRange: [0, 1],
outputRange: [-height, 0],
easing: Easing.inOut(Easing.ease)
});
return (
<View style={styles.container}>
<Text onPress={this.openHome}>Open up Home to start working on your app!</Text>
{
<Animated.View style={{ transform: [{ translateY }] }}>
<Home open={this.state.openHome}/>
</Animated.View>
}
</View>
);
}
}
Code for my Child Component which is Home.js:
const Home = props => {
if (!props.open) {
return (
<View style={styles.firstContainer}>
</View>
);
} else {
return (
<View style={styles.secondContainer}>
<Text>Hello</Text>
</View>
);
}
}
const styles = StyleSheet.create({
firstContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'transparent',
width: '100%'
},
secondContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'red',
width: '100%',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})

You need new Animated.Value(0) instead of new Value(0) in the first place, if you want to animate a value.

Related

How to scroll through an Image Carousel in React-Native expo using the Volume Buttons?

I am building an app for sheet music. I want to scroll through the images using the volume buttons. Why? Because ill modify a hands-free cable later to change the "volume" using a foot button. This is so the musician never leaves his instrument.
I used this snak to create an image carousel using expo. Now I want to incorporate the volume buttons.
CODE FROM THE SNAK:
import React, { Component } from 'react'
import { Animated, View, StyleSheet, Image, Dimensions, ScrollView , Text} from 'react-native'
const deviceWidth = Dimensions.get('window').width
const FIXED_BAR_WIDTH = 280
const BAR_SPACE = 10
const images = [
'https://s-media-cache-ak0.pinimg.com/originals/ee/51/39/ee5139157407967591081ee04723259a.png',
'https://s-media-cache-ak0.pinimg.com/originals/40/4f/83/404f83e93175630e77bc29b3fe727cbe.jpg',
'https://s-media-cache-ak0.pinimg.com/originals/8d/1a/da/8d1adab145a2d606c85e339873b9bb0e.jpg',
]
export default class App extends Component {
numItems = images.length
itemWidth = (FIXED_BAR_WIDTH / this.numItems) - ((this.numItems - 1) * BAR_SPACE)
animVal = new Animated.Value(0)
render() {
let imageArray = []
let barArray = []
images.forEach((image, i) => {
console.log(image, i)
const thisImage = (
<Image
key={`image${i}`}
source={{uri: image}}
style={{ width: deviceWidth }}
/>
)
imageArray.push(thisImage)
const scrollBarVal = this.animVal.interpolate({
inputRange: [deviceWidth * (i - 1), deviceWidth * (i + 1)],
outputRange: [-this.itemWidth, this.itemWidth],
extrapolate: 'clamp',
})
const thisBar = (
<View
key={`bar${i}`}
style={[
styles.track,
{
width: this.itemWidth,
marginLeft: i === 0 ? 0 : BAR_SPACE,
},
]}
>
<Animated.View
style={[
styles.bar,
{
width: this.itemWidth,
transform: [
{ translateX: scrollBarVal },
],
},
]}
/>
</View>
)
barArray.push(thisBar)
})
return (
<View
style={styles.container}
flex={1}
>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
scrollEventThrottle={10}
pagingEnabled
onScroll={
Animated.event(
[{ nativeEvent: { contentOffset: { x: this.animVal } } }]
)
}
>
{imageArray}
<View
style={styles.skip}
>
<Text style={{backgroundColor: '#fff',color:"#F44",textAlign:"center",alignItems: 'center',
justifyContent: 'center',}}>skip</Text>
</View>
</ScrollView>
<View
style={styles.barContainer}
>
{barArray}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
barContainer: {
position: 'absolute',
zIndex: 2,
bottom: 40,
flexDirection: 'row',
},
skip: {
position: 'absolute',
zIndex: 2,
bottom: 80,
flexDirection: 'row',
},
track: {
backgroundColor: '#ccc',
overflow: 'hidden',
height: 2,
},
bar: {
backgroundColor: '#5294d6',
height: 2,
position: 'absolute',
left: 0,
top: 0,
},
})
Requirements:
it must work with EXPO

React Native items are not layered properly with FlatList

I'm trying to use FlatList to stack custom-made dropdown menus on top of each other in React Native. I want the first dropdown to overlap the second while the second overlaps the third. The image below shows that the opposite is working, where the third dropdown overlaps the second while the second overlaps the first.
However, if I use the map method, it seems to work just fine.
import React from "react";
import { View, StyleSheet, FlatList } from "react-native";
import Dropdown from "../components/Dropdown";
import normalize from "react-native-normalize";
export default () => {
const arr = [0, 65, 130]; // controls the margin between the dropdowns // top
const layers = [3, 2, 1]; // Z-index
return (
<View style={styles.container}>
<FlatList // With FlatList
data={arr}
renderItem={({ item, index }) => (
<View style={[styles.dropdown, { top: item, zIndex: layers[index] }]}>
<Dropdown />
</View>
)}
/>
{/* {arr.map((spacing, index) => {
// With map
return (
<View
style={[styles.dropdown, { top: spacing, zIndex: layers[index] }]}
>
<Dropdown />
</View>
);
})} */}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
dropdown: {
position: "absolute",
},
cardContainer: {
top: "41%",
left: "37%",
height: normalize(134),
},
});
Dropdown code
import React, { useState, useRef } from "react";
import {
Animated,
Easing,
View,
Text,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import normalize from "react-native-normalize";
import DropDownIcon from "../assets/DownArrowIcon";
export default () => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const [toggle, setToggle] = useState(true); // controls the dropdown animation
const [accessLevels, setAccessLevels] = useState([
"Manager",
"Viewer",
"Admin",
]);
const height = normalize(33); // Initial height of the dropdown menu
const fadeIn = () => {
// Will change fadeAnim value to .5 in
Animated.timing(fadeAnim, {
toValue: 0.5,
easing: Easing.inOut(Easing.exp),
duration: 325,
}).start();
};
const fadeOut = () => {
// Will change fadeAnim value to 0
Animated.timing(fadeAnim, {
toValue: 0,
easing: Easing.inOut(Easing.exp),
duration: 250,
}).start();
};
const handleAnimation = () => {
setToggle((prev) => !prev);
toggle ? fadeIn() : fadeOut();
};
const handleSwap = (item) => {
// swap the selected item with the first item of the dropdown menu
let index = accessLevels.indexOf(item);
setAccessLevels((prevData) => {
let data = [...prevData];
let temp = data[0];
data[0] = item;
data[index] = temp;
return data; // We could order this in alphabetical order!
});
};
return (
<Animated.View
style={[
styles.dropdown,
{
height: fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [height, normalize(125)],
}),
},
]}
>
<TouchableWithoutFeedback onPress={handleAnimation}>
{/*First dropdown item*/}
<View
style={{
flexDirection: "row",
justifyContent: "space-evenly",
alignItems: "center",
height: normalize(34),
// color is based on the type of access level for the first dropdown item
backgroundColor:
accessLevels[0] === "Manager"
? "#019385"
: accessLevels[0] === "Viewer"
? "#376EC5"
: "#DECB52",
}}
>
<Text style={styles.dropdownText}>{accessLevels[0]}</Text>
<Animated.View // animation for the dropdown icon
style={{
transform: [
{
rotateX: fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
}),
},
],
}}
>
<DropDownIcon />
</Animated.View>
</View>
</TouchableWithoutFeedback>
<View // bottom two dropdown items
style={{
justifyContent: "space-evenly",
alignItems: "center",
minHeight: normalize(46),
flex: 1,
}}
>
<TouchableWithoutFeedback // second dropdown item
onPress={() => {
handleAnimation();
handleSwap(accessLevels[1]);
}}
>
<View style={styles.dropdownBottomItems}>
<Text style={{ color: "white" }}>{accessLevels[1]}</Text>
</View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback // third dropdown item
onPress={() => {
handleAnimation();
handleSwap(accessLevels[2]);
}}
>
<View style={styles.dropdownBottomItems}>
<Text style={{ color: "white" }}>{accessLevels[2]}</Text>
</View>
</TouchableWithoutFeedback>
</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
dropdown: {
backgroundColor: "#4E585E",
width: normalize(97),
borderRadius: 4,
overflow: "hidden",
},
dropdownText: {
color: "white",
},
dropdownBottomItems: {
width: "100%",
justifyContent: "center",
alignItems: "center",
height: normalize(24),
},
});

scaling a react-native button with animated

I'm creating a touchable button in react native with an animation. When the button is pressed, it should scale down a little bit. When the pressure is released, it should scale back to normal.
This is my code:
export const TouchableButton = (props) => {
const { onPress, text, icon } = props
const animatedValue = new Animated.Value(0)
const animatedValueInterpolateScale = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.95]
})
const pressInHandler = () => {
Animated.timing(
animatedValue,
{
toValue: 1,
duration: 150
}
).start()
}
const pressOutHandler = () => {
Animated.timing(
animatedValue,
{
toValue: 0,
duration: 150
}
).start()
}
return (
<TouchableWithoutFeedback onPress={onPress} onPressIn={pressInHandler} onPressOut={pressOutHandler}>
<View style={{ alignItems: 'center' }}>
<Animated.View style={{ width: '100%', height: 40, borderRadius: 5, overflow: 'hidden', transform: [{ scaleX: animatedValueInterpolateScale }, { scaleY: animatedValueInterpolateScale }] }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: Color.GrayLight }}>
<Text style={{ marginTop: 2.5, fontFamily: 'AlegreyaSans-Medium', fontSize: 15, color: Color.White }}>{text}</Text>
<View style={{ position: 'absolute', left: 12.5, top: 12.5 }}>
<Icon lib={icon.lib} icon={icon.icon} color={Color.White} size={15} />
</View>
</View>
</Animated.View>
</View>
</TouchableWithoutFeedback>
)
}
When the button is pressed, the animation in pressInHandler is started, and the scale is animated from 1 to 0.95. This works. But when I release the pressure (onPressOut is called), the scale snaps back to 1 without a smooth animation. It seems like pressOutHandler (and the animation in it) never is called.
I have another button with the same properties but instead of scaling I set the background color, and this works like it should.
Make it simple.
Note: ALWAYS USE useNativeDriver: true
const App = () => {
const animation = new Animated.Value(0);
const inputRange = [0, 1];
const outputRange = [1, 0.8];
const scale = animation.interpolate({inputRange, outputRange});
const onPressIn = () => {
Animated.spring(animation, {
toValue: 1,
useNativeDriver: true,
}).start();
};
const onPressOut = () => {
Animated.spring(animation, {
toValue: 0,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.container}>
<Animated.View style={[styles.button, {transform: [{scale}]}]}>
<TouchableOpacity
style={styles.btn}
activeOpacity={1}
onPressIn={onPressIn}
onPressOut={onPressOut}>
<Text style={styles.btnText}>BUTTON</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {flex: 1, alignItems: 'center', justifyContent: 'center'},
button: {
height: 70,
width: 200,
backgroundColor: 'red',
marginBottom: 20,
borderRadius: 10,
},
btn: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
btnText: {
color: '#fff',
fontSize: 25,
},
});
Here is a pretty simple solution without any animations which looks almost as native (at least on iOS):
import React from "react"
import { Pressable, PressableProps, StyleProp, ViewStyle } from "react-native"
type TouchableButtonProps = PressableProps & {
scale?: number;
style?: StyleProp<ViewStyle>;
}
const PressableScale: React.FC<TouchableButtonProps> = ({ scale, style, children, ...otherProps }) => {
return (
<Pressable style={({ pressed }) => [style, { transform: [{ scale: pressed ? (scale ?? 0.98) : 1 }] }]} {...otherProps}>
{children}
</Pressable>
)
}
Usage:
<PressableScale style={{ flex: 1, justifyContent: 'center', alignContent: 'center', backgroundColor: 'black', padding: 50, borderRadius: 12 }}>
<Text style={{ color: 'white' }}>This is pressable button</Text>
</PressableScale>

React Native : Bad performance animating the height of a view using react-native-reanimated

Animating the height of a view when scrolling through a list is very slow and choppy,
it's work fine for IOS, but not for Android :
import * as React from "react";
import { StyleSheet, Text } from "react-native";
import Animated from "react-native-reanimated";
const { Value, View, ScrollView, interpolate, Extrapolate } = Animated;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black"
},
listView: {
flex: 1
}
});
const IMAGE_MIN_HEIGHT = 300;
export default () => {
const animation = new Value(0);
const height = interpolate(animation, {
inputRange: [0, 125],
outputRange: [IMAGE_MIN_HEIGHT, 125],
extrapolate: Extrapolate.CLAMP
});
return (
<View style={styles.container}>
<View
style={{
backgroundColor: "red",
height: height,
width: "100%"
}}
></View>
<ScrollView
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
])}
scrollEventThrottle={1}
style={[styles.listView]}
>
{[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].map((elem, index) => (
<View
style={{ width: "100%", height: 100, marginBottom: 20 }}
key={index}
>
<Text style={{ color: "white", fontSize: 30 }}>{elem}</Text>
</View>
))}
</ScrollView>
</View>
);
};
Is there anything that can be done to make it smoother?
the solution is to set Header position to absolute, i don't know why but it's working fine :
import * as React from "react";
import { StyleSheet, Text, Platform } from "react-native";
import Animated from "react-native-reanimated";
const { Value, View, ScrollView, interpolate, Extrapolate } = Animated;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black"
},
listView: {
flex: 1
}
});
const IMAGE_MIN_HEIGHT = 300;
export default () => {
const animation = new Value(0);
const height = interpolate(animation, {
inputRange: [0, 125],
outputRange: [IMAGE_MIN_HEIGHT, 125],
extrapolate: Extrapolate.CLAMP
});
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle={{ paddingTop: IMAGE_MIN_HEIGHT }}
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
])}
scrollEventThrottle={1}
style={[styles.listView]}
>
{[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].map((elem, index) => (
<View
style={{ width: "100%", height: 100, marginBottom: 20 }}
key={index}
>
<Text style={{ color: "white", fontSize: 30 }}>{elem}</Text>
</View>
))}
</ScrollView>
<View
style={{
backgroundColor: "red",
height: height,
width: "100%",
position: "absolute",
top: Platform.OS == "ios" ? 20 : 0,
left: 0,
right: 0
}}
></View>
</View>
);
};
Try using NativeDriver for Android.
Also, for better performance, you should use FlatList or SectionList here instead of ScrollView.
<Animated.ScrollView
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
],
{ useNativeDriver: true } // Add this line
)}
scrollEventThrottle={1}
style={[styles.listView]}
>
//
</Animated.ScrollView>
For more details, please see this

How to do a simple opacity animation using functional components and react native reanimated

Every document of react reanimated is using class components i've tried to change the way of doing a simple opacity animation of a button but i can't for the life of me make it work, it seems set is not working, if i change set to Opacity.setvalue(0) it works but i need set to in order to use clock for other animations, any help please.
import React, { useState } from "react";
import { View, Text, StyleSheet, Image, Dimensions } from "react-native";
import Animated, { Easing } from "react-native-reanimated";
import { TapGestureHandler, State } from "react-native-gesture-handler";
function MusicApp() {
const {
Value,
Clock,
startClock,
stopClock,
debug,
timing,
clockRunning,
block,
cond,
set,
eq
} = Animated;
const { width, height } = Dimensions.get("window");
const [buttonOpacity, setbuttonOpacity] = useState(1);
const Opacity = new Value(1);
function onStateChange() {
Animated.event([
{
nativeEvent: ({state}) =>
Animated.block([cond(eq(state, State.END),set(Opacity,0) )
])
}
]);
}
return (
<View style={{ backgroundColor: "black", justifyContent: "flex-end" }}>
<View
style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0 }}
>
<Image
source={require("../assets/bg.jpg")}
style={{ height: height, width: width }}
/>
</View>
<View style={{ height: height / 3, justifyContent: "center" }}>
<TapGestureHandler
onHandlerStateChange={() => {
onStateChange();
}}
>
<Animated.View style={{ ...styles.button, opacity: Opacity}}>
<Text style={{ fontSize: 20 }}>SIGN IN</Text>
</Animated.View>
</TapGestureHandler>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
button: {
backgroundColor: "red",
height: 70,
marginHorizontal: 20,
borderRadius: 35,
alignItems: "center",
justifyContent: "center"
},
button2: {
backgroundColor: "blue",
height: 70,
marginHorizontal: 20,
borderRadius: 35,
alignItems: "center",
justifyContent: "center"
}
});
export default MusicApp;
Reanimated uses declarative api rather than imperative api in default Animated api i.e .start() .setValue().
Clock() is special node in reanimated , which is under ther hood just a value which updated timestamp per every frame.
using this clock, you can define helper functions like
function runTiming(clock, value, dest) {
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 5000,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
return block([
cond(
clockRunning(clock),
[
// if the clock is already running we update the toValue, in case a new dest has been passed in
set(config.toValue, dest),
],
[
// if the clock isn't running we reset all the animation params and start the clock
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.frameTime, 0),
set(config.toValue, dest),
startClock(clock),
]
),
// we run the step here that is going to update position
timing(clock, state, config),
// if the animation is over we stop the clock
cond(state.finished, debug('stop clock', stopClock(clock))),
// we made the block return the updated position
state.position,
]);
}
and in component, there are multiple ways to animate a value.
i recommed using Code or useCode from reanimated
you can use it like
function OpacityButton({}) {
const opacity = new Value(1);
const clock = new Clock();
const clicked = new Value(0);
const buttonState = new Value();
useCode(() => block([cond(eq(clicked, 1), set(opacity, runTiming(clock, opacity, 0.5)))]));
return (
<TouchableOpacity onPress={() => clicked.setValue(1)}>
<Animated.View style={[styles.button, { opacity }]} />
</TouchableOpacity>
);
}
this is just 70% of code. But you may extend it to your requirement.
You can map the tap state of the button to a tap variable. Then you can use the useCode hook with tap as a dependency and change the opacity from 1 to 0 accordingly.
const { height } = Dimensions.get('window');
const { eq, useCode, timing, Clock, Value, set, cond } = Animated;
const App = () => {
const [tap, setTap] = useState(new Value(-1));
const opacity = new Value(1);
const handleChange = (e) => {
tap.setValue(e.nativeEvent.state);
};
useCode(() => cond(eq(tap, State.BEGAN), set(opacity, 0), 0), [tap]);
return (
<>
<View
style={{
height: height / 3,
justifyContent: 'flex-end',
backgroundColor: 'green',
}}
>
<TapGestureHandler onHandlerStateChange={handleChange}>
<Animated.View style={{ ...styles.button, opacity }}>
<Text style={{ fontSize: 20 }}>SIGN IN</Text>
</Animated.View>
</TapGestureHandler>
</View>
</>
);
};