react-native-reanimated interpolate width percentage - react-native

I am converting some of my default Animated stuff to react-native-reanimated v2 and can't seem to figure out how to interpolate a value to a string percentage. I also can't find anything related to this in the docs.
Using Animated from react-native I could just do:
width: widthAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0%', '100%'],
})
But the interpolate() function from reanimated only takes number params, so how can I animate the width then?
interpolate(widthAnim.value, [0, 1], ['0%', '100%'])
// err: Type 'string' is not assignable to type 'number'.

You can simply use template literal
width: `${widthAnim.value * 100}%`
Check out more official example

A full example to complete the previous response:
const searchViewAnimatedWidth = useSharedValue(0);
const searchBoxAnimatedStyle = useAnimatedStyle(() => {
const interpolatedWidth = interpolate(
searchViewAnimatedWidth.value,
[0, 1],
[40, 100],
{ extrapolateRight: Extrapolation.CLAMP }
);
return {
width: withTiming(`${interpolatedWidth}%`, {
duration: 500,
useNativeDriver: false,
}),
};
});
<Animated.View
style={{...otherStyles,...searchBoxAnimatedStyle}}>
...
</Animated.View>
And in your trigger
onPress={() => {
searchViewAnimatedWidth.value = searchViewAnimatedWidth.value == 0 ? 1 : 0;
}}

Related

Set reanimated values back to original after click

I have made a bit of re-usable animation code. It works great but I an unsure if it is possible to get the animations back to their original values after a button is pressed easily.
The only way I can think of it to use useShared Values to store all the before and after values and set them as required but this would involve alot of values but because the animations have already ran, there must be a way to just take them back to their original start?
The code I am using for the animations is : -
EntryAnimation.js
import React, { useEffect } from 'react';
import Animated, {
useAnimatedStyle,
useSharedValue,
useDerivedValue,
interpolate,
withDelay,
withTiming,
withSpring,
Easing,
} from 'react-native-reanimated';
export const EntryAnimation = ({
children,
index,
rotateV,
scaleV,
offsetXV,
offsetYX,
}) => {
const play = useSharedValue(play);
const progress = useDerivedValue(() => {
return play.value
? withDelay(50 * (index ?? 0), withSpring(1, { duration: 350 }))
: 0;
});
useEffect(() => {
play.value = play;
}, []);
const animatedStyle = useAnimatedStyle(() => {
// const opacity = interpolate(progress.value, [0, 1], [0, 1]);
const translateY = interpolate(progress.value, [0, 1], [0, offsetYX]);
const translateX = interpolate(progress.value, [0, 1], [0, offsetXV]);
const rotate = interpolate(progress.value, [0, 1], [0, rotateV]);
const scale = interpolate(progress.value, [0, 1], [1, scaleV]);
return {
transform: [{ translateY }, { translateX }, { rotate }, { scale }],
};
});
return <Animated.View style={animatedStyle}>{children}</Animated.View>;
};
And to use on an element in my main code, I use :-
<EntryAnimation
index={1}
rotateV={0}
scaleV={0.8}
offsetXV={0}
offsetYX={-270}>
<Animated.Image
source={{ uri: item.poster }}
style={[styles.posterImage, { zIndex: 6 }]}
/>
</EntryAnimation>
I have tried using the code below but because its in a ternary statement I am getting errors?
{animStarted ? (
<EntryAnimation
index={1}
rotateV={0}
scaleV={0.8}
offsetXV={0}
offsetYX={-270}
>
) : (
<EntryAnimation
index={1}
rotateV={0}
scaleV={1}
offsetXV={0}
offsetYX={0}
>
)}
Any ideas?

React native confirmation code field values

Hi I'm using this package as component it shwos and get vlaues but how can I get input values from root component.
in root page
import AnimatedVerification from '../../components/AnimatedVerification/AnimatedVerification';
inside this code is all comes from this example
I just want to get this 4 number values from root component how to make it. Thank you
Expo
/*
*/
import {Animated, Image, SafeAreaView, Text, View} from 'react-native';
import React, {useState} from 'react';
import {
CodeField,
Cursor,
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
import styles, {
ACTIVE_CELL_BG_COLOR,
CELL_BORDER_RADIUS,
CELL_SIZE,
DEFAULT_CELL_BG_COLOR,
NOT_EMPTY_CELL_BG_COLOR,
} from './styles';
const {Value, Text: AnimatedText} = Animated;
const CELL_COUNT = 4;
const source = {
uri:
'https://user-images.gitx.com/4xxxxxxx.png',
};
const animationsColor = [...new Array(CELL_COUNT)].map(() => new Value(0));
const animationsScale = [...new Array(CELL_COUNT)].map(() => new Value(1));
const animateCell = ({hasValue, index, isFocused}) => {
Animated.parallel([
Animated.timing(animationsColor[index], {
useNativeDriver: false,
toValue: isFocused ? 1 : 0,
duration: 250,
}),
Animated.spring(animationsScale[index], {
useNativeDriver: false,
toValue: hasValue ? 0 : 1,
duration: hasValue ? 300 : 250,
}),
]).start();
};
const AnimatedVerification = () => {
const [value, setValue] = useState('');
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value,
setValue,
});
const renderCell = ({index, symbol, isFocused}) => {
const hasValue = Boolean(symbol);
const animatedCellStyle = {
backgroundColor: hasValue
? animationsScale[index].interpolate({
inputRange: [0, 1],
outputRange: [NOT_EMPTY_CELL_BG_COLOR, ACTIVE_CELL_BG_COLOR],
})
: animationsColor[index].interpolate({
inputRange: [0, 1],
outputRange: [DEFAULT_CELL_BG_COLOR, ACTIVE_CELL_BG_COLOR],
}),
borderRadius: animationsScale[index].interpolate({
inputRange: [0, 1],
outputRange: [CELL_SIZE, CELL_BORDER_RADIUS],
}),
transform: [
{
scale: animationsScale[index].interpolate({
inputRange: [0, 1],
outputRange: [0.2, 1],
}),
},
],
};
// Run animation on next event loop tik
// Because we need first return new style prop and then animate this value
setTimeout(() => {
animateCell({hasValue, index, isFocused});
}, 0);
return (
<AnimatedText
key={index}
style={[styles.cell, animatedCellStyle]}
onLayout={getCellOnLayoutHandler(index)}>
{symbol || (isFocused ? <Cursor /> : null)}
</AnimatedText>
);
};
return (
<SafeAreaView style={styles.root}>
<CodeField
ref={props.ref}
{...props}
value={value}
onChangeText={setValue}
cellCount={CELL_COUNT}
rootStyle={styles.codeFiledRoot}
keyboardType="number-pad"
textContentType="oneTimeCode"
renderCell={renderCell}
/>
</SafeAreaView>
);
};
export default AnimatedVerification;
If I understand, you want to get the value of the child component in parent component ?
Your CodeField component has a ref in the parent component ref={props.ref}.
In your CodeField component define a method who return the inputs values. And in the parent you can call the method with the ref.

react-navigation accessing navigation state in cardStyleInterpolator

I have a Stack Navigator that uses either a modal or slide transition depending on the screen.
i.e. a transition sequence might look like:
HomeScreen -Slide> Screen1 -Modal From Bottom> Screen2
This used to not be a problem, but the updated modal transitions cause there to be noticeable "glitches" due to the varying card interpolation styles. I know I can fix this by adjusting the cardStyle interpolation values based on the current/previous screen.
Is there any way to access the navigation object for the Stack Navigator when creating the cardStyleInterpolator?
EDIT:
cardStyleInterpolator (incomplete):
export const cardStyleInterpolator = ({
index,
current,
next,
inverted,
layouts: { screen },
insets,
}) => {
const isLandscape = screen.width > screen.height;
const topOffset = isLandscape ? 0 : 10;
const statusBarHeight = insets.top;
const aspectRatio = screen.height / screen.width;
const progress = add(current.progress, next ? next.progress : 0);
const translateY = multiply(
progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
screen.height,
index === 0 || index === 1 ? 0 : topOffset,
(index === 0 || index === 1 ? statusBarHeight : 0) - topOffset * aspectRatio,
],
}),
inverted
);
const overlayOpacity = progress.interpolate({
inputRange: [0, 1, 1.0001, 2],
outputRange: [0, 0.3, 1, 1],
});
const scale = isLandscape
? 1
: progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
1,
1,
screen.width ? 1 - (topOffset * 2) / screen.width : 1,
],
});
const borderRadius = isLandscape
? 0
: index === 0 || index === 1
? progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [0, 0, 10],
})
: 10;
return {
cardStyle: {
overflow: 'hidden',
borderTopLeftRadius: borderRadius,
borderTopRightRadius: borderRadius,
marginTop: index === 0 || index === 1 ? 0 : statusBarHeight,
transform: [{ translateY }, { scale }],
},
overlayStyle: { opacity: overlayOpacity },
};
};
This cardStyleInterpolator is a modified version of the forModalPresentationIOS. The modification of adding index === 1 to the conditionals helps the transition from Screen2 -> Screen1. Since we pushed from HomeScreen to Screen1, when we nav back to Screen1 we want it to animate as if it was the initial route and not another modal.
The current forModalPresentationIOS assumes that index 0 is always the index where we use the modal transition from, but when overriding the cardStyleInterpolator of index 1 to use the slide transition causes this assumption to be invalid.

Keyboard listener can't change this.state

I want an animation to be executed when i open my keyboard and revert back when I close it.
The problem is:
undefined is not an object (evaluating 'this.state.scaleValue')
I have Keyboard.listener which works good.
State:
this.state = {
scaleValue: new Animated.Value(0),
}
Animated View
<Animated.View style={styles.logoContainer,
{
transform: [
{scale: logoScale}
]
}
}>
<Image source={require('./someimage.png')} style={{width: 64, height: 64}} />
</Animated.View>
Interpolate
const logoScale = this.state.scaleValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 0.5, 0]
});
Trigger
_keyboardDidShow () {
this.state.scaleValue.setValue(0);
Animated.timing(
this.state.ScaleValue,
{
toValue: 1,
duration: 300,
easing: Easing.easeOutBack
}
).start();
}
So, when I put the code from trigger into a function and call it onPress with Touchable, it works.
this does not have the same context inside the function supplied to Animated.timing.
You need to bind the method:
this._keyboardDidShow = this._keyboardDidShow.bind(this)
If that doesn't work you could probably even set let me = this above the call to timing (and refer to it as me within the body of the timing method call).

React Native: react-native-maps. Animate active marker depending on ScrollView offset

I am using react-native-maps and want to gradually animate scale of active and inactive markers depending on the ScrollView x offset. (the below image describes it better)
My approach is to create new Animated.Value for each marker and Interpolate ScrollView offset for each marker:
//Container
constructor(props) {
super(props);
this.state = {
...
scale: [],
opacity: []
}
}
componentWillReceiveProps(props) {
if (props.places.length) {
const scale = [];
const opacity = [];
props.places.forEach((xx, ii) => {
this.state[`scale${ii}`] = new Animated.Value(0.5);
this.state[`scale${ii}`] = this._scrollX.interpolate({
inputRange: [vw * (ii - 1), vw * ii, vw * (ii + 1)],
outputRange: [0.5, 1, 0.5],
extrapolate: 'clamp'
});
scale.push(this.state[`scale${ii}`]);
this.state[`opacity${ii}`] = new Animated.Value(0);
this.state[`opacity${ii}`] = this._scrollX.interpolate({
inputRange: [vw * (ii - 1), vw * ii, vw * (ii + 1)],
outputRange: [0, 1, 0],
extrapolate: 'clamp'
});
opacity.push(this.state[`opacity${ii}`]);
});
this.setState({
scale,
opacity,
});
}
render() {
return (
<PlacesMap scale={this.state.scale} opacity={this.state.opacity} />
<ScrollView onScroll={this._onScroll} ... />
)
}
_onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { x: this._scrollX }}}],
{ useNativeDriver: true }
);
//PlacesMap
....
render {
return (
<MapView.Animated>
{ places &&
places.map((place, ii) => {
return (
<PlaceMarker
key={place.id.toString()}
place={place}
scale={scale[ii]}
opacity={opacity[ii]}
/>
)
})
}
</MapView.Animated>
)}
Maybe there is a more elegant (performant) approach that I don't know, where I don't need to create new Animated.Value and Interpolators for each marker and with which I can get the same result?