React native reanimated v2 Scale + Opacity withRepeat - reactjs

I am trying to implement scaling View with Opacity togather through React native reanimated v2, but not able to contol withRepeat ...
Below code is just Perform scaling withRepeat but not Opacity. How to control Opacity + Scaling of view withRepeat ... Want to apply scaling and Opacity both on view in loop/Repeat.
import React, { useState } from 'react';
import { View, TouchableWithoutFeedback } from 'react-native';
import Animated,
{ withRepeat, useSharedValue, interpolate, useAnimatedStyle, useDerivedValue, withTiming }
from 'react-native-reanimated'
import Styles from './Styles';
function LoopApp() {
const [state, setState] = useState(0);
const scaleAnimation = useSharedValue(1);
const animationOpacityView = useSharedValue(1);
scaleAnimation.value = withRepeat(withTiming(2.5, { duration: 2000 }), -1, true);
//animationOpacityView.value = withRepeat(0, -1, true);
const debug = useDerivedValue(() => {
// console.log(scaleAnimation.value);
return scaleAnimation.value;
});
const growingViewStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: scaleAnimation.value }],
opacity: withTiming(animationOpacityView.value, {
duration: 1500
}, () => {
animationOpacityView.value = 0.99
})
};
});
return (
<View style={Styles.container}>
<Animated.View style={[Styles.viewStyle, growingViewStyle]} />
</View>
);
}
export default LoopApp;
Style.js
import {DevSettings, Dimensions, I18nManager} from 'react-native';
import Constants from '../../common/Constants';
const Screen = {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
};
export default {
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
viewStyle: {
backgroundColor: '#19a35c',
width: Screen.width * 0.0364,
height: Screen.width * 0.0364,
borderRadius: 100,
},
};

Related

How to trigger Navigation when an Reanimated 2 Animation is completed? React Native - Reanimated 2 & React-Navigation

I'm trying to create a Tinder clone where there is a left and right swiping mechanism for liking and disliking a profile. However, I want the user to swipe the profile downwards to open the profile details screen. I have tried creating the function directly in ".onEnd" but every time I perform the swipe action, it completely crashes the app. Every other swipe direction works perfectly as expected. I have tried resetting the cache, uninstalling the app on the emulator and that hasn't fixed it. I couldn't find a solution on the documentation or anywhere on Google.
Thanks for the help in advance! This is my first programming project! :D
Here's my current code:
import React from "react";
import { View, Text, Image, StyleSheet } from 'react-native'
import LinearGradient from "react-native-linear-gradient";
import ListingDetails from "../ListingDetails";
import { listingsArr } from "../Listings";
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import { useWindowDimensions } from 'react-native';
import { useNavigation } from '#react-navigation/native';
import ListingDetailScreen from "../../Screens/ListingDetailScreen";
import {MainStackNavigator } from '../../Navigation/MainStackNavigator';
const SingleCard = (props) => {
const navigation = useNavigation();
const navigateToHome = () => {navigation.navigate('Home')}
console.log(useWindowDimensions().height)
const windowWidth = useWindowDimensions().width
const windowHeight = useWindowDimensions().height
const startPosition = 0;
const x = useSharedValue(startPosition)
const y = useSharedValue(startPosition)
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: withSpring(x.value) },
{ translateY: withSpring(y.value) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});
const start = useSharedValue({ x: 0, y: 0 });
const gesture = Gesture.Pan()
.onBegin(() => {
isPressed.value = true
})
.onUpdate((e) => {
x.value = startPosition + e.translationX;
y.value = startPosition + e.translationY;
})
.onEnd((e) => {
const verticalSwipe = Math.abs(y.value) - Math.abs(x.value);
const horizontalSwipe = Math.abs(x.value) - Math.abs(y.value);
console.log(verticalSwipe)
console.log(y.value)
if (verticalSwipe >= 0) {
if (y.value > windowHeight / 4) {
navigateToHome();
console.log('swiped up')
} else {
x.value = withSpring(startPosition);
y.value = withSpring(startPosition);
}
} else {
if (x.value > windowWidth / 4) {
x.value = withSpring(windowWidth * 2);
y.value = withSpring(startPosition)
} else if (x.value < windowWidth / -4) {
x.value = withSpring(windowWidth * -2);
y.value = withSpring(startPosition);
console.log('swiped left')
} else {
x.value = withSpring(startPosition);
y.value = withSpring(startPosition);
}
}
})
.onFinalize(() => {
isPressed.value = false;
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.masterContainer, animatedStyles]} >
<View style={styles.spacerContainer}>
</View>
<Text> This is single card component </Text>
<Image style={styles.imageStyle} source={props.cardListing.Photo} />
<LinearGradient colors={['rgba(255,255,255,0)', 'rgba(0,0,0,0.4 )']} style={styles.backgroundOverlay} />
<ListingDetails myListing={props.cardListing} />
</Animated.View>
</GestureDetector>
)
}
const styles = StyleSheet.create({
spacerContainer: {
flex: 2,
},
containerStyle: {
flex: 1,
},
imageStyle: {
position: 'absolute',
resizeMode: 'cover',
width: '100%',
height: '100%',
flex: 1,
},
imageContainer1: {
position: 'absolute',
width: '100%',
height: '100%',
zIndex: 0,
flex: 1
},
imageContainer2: {
position: 'absolute',
width: '100%',
height: '100%',
flex: 1,
zIndex: 0,
},
backgroundOverlay: {
flex: 1,
width: '100%',
height: '100%',
resizeMode: 'cover',
position: 'absolute',
zIndex: 0,
},
masterContainer: {
width: '100%',
height: '100%',
}
})
export default SingleCard;
You must use RunOnJS from reanimated lib like example below:
Explaining, animated is running in UI Thread and navigation.navigate function run in another thread (JS Thread), then animated can't call navigate function.
More in reanimated documentation.
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
// ...other codes
const settingsIconRotation = useSharedValue(0);
const onPressFn = () => {
// function from other thread (different of reanimated thread)
navigation.navigate('Modal');
};
const onPressSettings = () => {
settingsIconRotation.value = withSpring(180, {}, (finished) => {
if (finished) {
settingsIconRotation.value = 0;
runOnJS(onPressFn)(); // execute on JS thread
} else {
}
});
};
// ...other codes

interpolateNode vs interpolate : interpolate causes jank while animating height while interpolateNode doesn't

I have just started using react-native-reanimated. And I would like to use reanimated v2 apis (newer ones).
Here, in this example, there is a considerable performance drop using interpolateNode and interpolate
Here's the example
import * as React from 'react';
import { Text, View, StyleSheet, Dimensions, Image } from 'react-native';
import Constants from 'expo-constants';
import Animated, {
useSharedValue,
useAnimatedScrollHandler,
Extrapolate,
useAnimatedStyle,
interpolate,
interpolateNode,
} from "react-native-reanimated";
export const HEADER_IMAGE_HEIGHT = Dimensions.get("window").width / 3
const styles = StyleSheet.create({
image: {
position: "absolute",
top: 0,
left: 0,
width: '100%',
resizeMode: "cover",
},
});
const IMAGE_URI = 'https://i.pinimg.com/originals/a4/1a/e5/a41ae5ff09234422737d3899cc895184.jpg'
const touchX = 100;
const heightInputRange = [-touchX, 0];
const heightOutputRange = [HEADER_IMAGE_HEIGHT + touchX, HEADER_IMAGE_HEIGHT];
const heightAnimConfig = {
extrapolateRight: Extrapolate.CLAMP,
}
const topInputRange = [0, touchX];
const topOutputRange = [0, -touchX];
const topAnimConfig = {
extrapolateLeft: Extrapolate.CLAMP,
}
export default function App() {
// const scrollY = useSharedValue(0)
// const scrollHandler = useAnimatedScrollHandler({
// onScroll: (e) => {
// scrollY.value = e.contentOffset.y;
// },
// });
// const animStyles = useAnimatedStyle(() => {
// return {
// height: interpolate(scrollY.value, heightInputRange, heightOutputRange),
// top: interpolate(scrollY.value, topInputRange, topOutputRange),
// }
// })
const scrollY = new Animated.Value(0);
const scrollHandler = Animated.event([
{
nativeEvent: {
contentOffset: {
y: scrollY
}
}
}
])
const animStyles = {
height: interpolateNode(scrollY, {
inputRange: heightInputRange,
outputRange: heightOutputRange,
extrapolateRight: Extrapolate.CLAMP,
}),
top: interpolateNode(scrollY, {
inputRange: topInputRange,
outputRange: topOutputRange,
extrapolateLeft: Extrapolate.CLAMP,
}),
}
return (
<View style={{ flex: 1 }}>
<Animated.Image
style={[styles.image, animStyles]}
source={{ uri: IMAGE_URI }}
/>
<Animated.ScrollView
scrollEventThrottle={1}
style={StyleSheet.absoluteFill}
onScroll={scrollHandler}
>
<View style={{ height: HEADER_IMAGE_HEIGHT + 200 }} />
{Array.from({ length: 50 }).map((v, idx) => {
return (
<View key={idx}>
<Text>{idx}</Text>
</View>
)
})}
</Animated.ScrollView>
</View>
);
}
You could check up the snack here :- https://snack.expo.io/#sapien/tactless-orange.
Questions :-
What is the difference between interpolate and interpolateNode?
How to choose between using either of them?
Why is one much performant than the other?
React Native Reanimated version 1 used interpolate. Since they wanted you to be able to use old API (backwards compatibility, etc), in version 2, they introduced interpolateNode.
interpolateNode has a much better performance than interpolate.
because interpolateNode is newer. They used a lot of different stuff to make it work better.

React Native doesn't update state

I'm writing a simple animation and trying to reset it after it's complete
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Image, Animated, Easing } from 'react-native';
export default function App() {
const [leftPos, setLeftPos] = useState(new Animated.Value(0))
useEffect(() => {
cycleAnimation()
}, []);
const cycleAnimation = () => {
console.log('STARTING ANIMATION:', leftPos)
Animated.sequence([
Animated.timing(
leftPos,
{
toValue: 430,
duration: 3000,
easing: Easing.linear,
useNativeDriver: false
}
)
]).start(() => {
setLeftPos(new Animated.Value(0))
cycleAnimation()
})
}
return (
<View style={{ backgroundColor: 'black', flex: 1 }}>
<Animated.View style={{ left: leftPos }}>
<Image
style={styles.cloud}
source={require('./assets/cloud.png')}
/>
</Animated.View>
</View >
);
}
const styles = StyleSheet.create({
cloud: {
}
});
leftPos is always 430 (except for the first iteration) in cycleAnimation despite calling setLeftPos with new 0 value. Also tried putting cycleAnimation in setLeftPos's callback but got the same result.
You are resetting it incorrectly. No need to update state, try this:
leftPos.setValue(0);
Put your function inside useEffect
import React, { useState, useEffect } from "react";
import { StyleSheet, View, Image, Animated, Easing } from "react-native";
export default function App() {
const [leftPos, setLeftPos] = useState(new Animated.Value(0));
useEffect(() => {
const cycleAnimation = () => {
console.log("STARTING ANIMATION:", leftPos);
Animated.sequence([
Animated.timing(leftPos, {
toValue: 430,
duration: 3000,
easing: Easing.linear,
useNativeDriver: false,
}),
]).start(() => {
setLeftPos(new Animated.Value(0));
});
};
cycleAnimation();
}, [leftPos]);
return (
<View style={{ backgroundColor: "black", flex: 1 }}>
<Animated.View style={{ left: leftPos }}>
<Image style={styles.cloud} source={require("./assets/icon.png")} />
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
cloud: {},
});

React Native Expo: Camera in ViewPager

I am creating a ViewPager with a Camera inside in a View, when the ViewPager renders the Views everything is ok but then when the ViewPager change the page and get back to the Camera Page the Camera is not appearing again. How to solve this? There is a way to render the camera asynchronously?
This is my ViewPager:
import React from 'react';
import ViewPager from '#react-native-community/viewpager';
import { View } from 'react-native';
const Pager = ({
pages,
initalPage,
onPageSelected,
onPageScrollStateChanged,
onPageScroll
}) => {
return (
<ViewPager
style={{flex: 1}}
initialPage={initalPage}
onPageSelected={(e)=>onPageSelected && onPageSelected(e.nativeEvent)}
onPageScrollStateChanged={(e)=>onPageScrollStateChanged && onPageScrollStateChanged(e.nativeEvent)}
onPageScroll={(e)=>onPageScroll && onPageScroll(e.nativeEvent)}
>
{
pages.map((page,i)=>
<View key={i} style={{flex: 1}}>
{
page
}
</View>
)
}
</ViewPager>
)
}
export default Pager;
This is my Camera Page:
import React, { useEffect, Suspense, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { Camera } from 'expo-camera';
import { LinearGradient } from 'expo-linear-gradient';
import { color } from '../../utils';
const ScannerView = ({
isInView,
}) => {
let camera = null;
const [hasPermission, setHasPermission] = useState(null);
const [cameraRatio, setCameraRatio] = useState('1:1');
useEffect(()=>{
(async () => {
const { status } = await Camera.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleCameraReady = () => {
camera.getSupportedRatiosAsync().then((data)=>{
setCameraRatio(data[data.length-1]);
});
}
const handleBarcodeScanned = (data) => {
console.log(data);
}
const handleThis = (err) => {
console.log("EVENT",err.nativeEvent)
}
const renderCamera = (
<Camera
ref={ref=>{
camera=ref;
}}
style={styles.camera}
type={Camera.Constants.Type.back}
focusable={true}
ratio={cameraRatio}
onCameraReady={handleCameraReady}
onBarCodeScanned={handleBarcodeScanned}
onMountError={handleThis}
/>
)
return (
<View style={styles.container}>
{
(hasPermission) &&
<Suspense>
{renderCamera}
</Suspense>
}
<LinearGradient
colors={['transparent', color.neutral80]}
style={styles.gradient}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black',
alignItems: 'stretch'
},
camera: {
flex: 1,
backgroundColor: 'red',
},
gradient: {
flex: 1,
height: 220,
position: 'absolute',
bottom: 0,
left: 0,
right: 0
}
})
export default ScannerView;

Create a loop with react-native-reanimated

I'm trying to create a Blinking function in an App using hooks and react-navigation 5 - No classes.
"react-native-reanimated" is new to me. I'm more familiar with Animated so this is why I need some help here. Thank you!
import React, { useState, useEffect, Component, useCallback } from "react";
import {
StyleSheet,
Text,
View
} from "react-native";
import Animated, { Easing } from "react-native-reanimated";
import { loop } from "react-native-redash";
function BlinkIt(props){
const [fadeAnim] = useState(new Animated.Value(0));
useEffect(() => {
Animated.set(
fadeAnim,
loop({
duration: 5000,
autoStart: true,
boomerang: true
})
)
}, []);
return (
<Animated.View // Special animatable View
style={{
...props.style,
opacity: fadeAnim
}}
>
{props.children}
</Animated.View>
);
}
export default function App() {
return (
<View style={styles.container}> <BlinkIt><Text>The text is blinking</Text></BlinkIt></View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
I made a video tutorial on how to create loop animation with react-native-reanimated https://youtu.be/W82p3WfwxrA. Or check code listing below
import React, {useMemo, useState, useEffect} from 'react';
import {
TouchableWithoutFeedback,
Image,
StyleSheet,
Dimensions,
} from 'react-native';
import Animated, { Easing, stopClock } from 'react-native-reanimated';
const imageSize = {
width: 256,
height: 256,
};
const screenWidth = Dimensions.get('window').width;
const animatedWidth = screenWidth + imageSize.width;
const {
useCode,
block,
set,
Value,
Clock,
eq,
clockRunning,
not,
cond,
startClock,
timing,
interpolate,
and,
} = Animated;
const runTiming = (clock) => {
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 5000,
toValue: 1,
easing: Easing.inOut(Easing.linear),
};
return block([
// we run the step here that is going to update position
cond(
not(clockRunning(clock)),
set(state.time, 0),
timing(clock, state, config),
),
cond(eq(state.finished, 1), [
set(state.finished, 0),
set(state.position, 0),
set(state.frameTime, 0),
set(state.time, 0),
]),
state.position,
]);
}
export const AnimatedBackground = () => {
const [play, setPlay] = useState(false);
const {progress, clock, isPlaying} = useMemo(
() => ({
progress: new Value(0),
isPlaying: new Value(0),
clock: new Clock(),
}),
[],
);
useEffect(() => {
isPlaying.setValue(play ? 1 : 0);
}, [play, isPlaying]);
useCode(
() =>
block([
cond(and(not(clockRunning(clock)), eq(isPlaying, 1)), startClock(clock)),
cond(and(clockRunning(clock), eq(isPlaying, 0)), stopClock(clock)),
set(progress, runTiming(clock)),
]),
[progress, clock],
);
return (
<TouchableWithoutFeedback
style={styles.container}
onPress={() => setPlay(!play)}
>
<Animated.View style={[styles.image, { opacity: progress }]}>
<Image
style={styles.image}
source={require('./cloud.png')}
resizeMode="repeat"
/>
</Animated.View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
width: animatedWidth,
height: '100%',
},
});

Resources