Animation does not work properly in react native - react-native

I tried making a carousel by watching a tutorial but I cannot get it to work for an event driven animation. Instead of animating it just updates the position to new location.
This does not happen if I use only one type of animation for transition, mentioning just one value to transform rotate instead of passing an expression.
what it looks like
what it should look like
const cards = ["tomato", "teal", "pink"]
const alpha = Math.PI/6
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current
const [toggled, setToggled] = React.useState(false)
const animationFn = () => {
Animated.spring(value, {
toValue: 1,
friction: 10,
useNativeDriver: true
}).start()
setToggled(toggled => !toggled)
}
const rotateOpen = (rotate) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`]
})
}
const rotateClose = (rotate, maxValues) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: [`${maxValues}rad`, `${rotate}rad`]
})
}
return(
<>
{cards.map((card, index) => {
const rotate = toggled ? (index - 1) * alpha : 0
const maxValues = (index-1) * alpha
return (
<Animated.View key={card} style={{transform: [
{translateY: -50},
{translateX: -100},
{rotate: !toggled ? rotateOpen(rotate) : rotateClose(rotate, maxValues) },
{translateX: 100},
], borderRadius: 15, position: 'absolute', backgroundColor: card, height: 100, width: 200}} />
)
})}
<View style={{paddingTop: 100}}>
<TouchableOpacity onPress={() => { animationFn() }}>
<Text style={{fontSize: 30}}> Animate </Text>
</TouchableOpacity>
</View>
</>
)
}

Your interpolation values shouldn't change between the open and close functions. The animation library knows that when you go from 0 to 1, you're rotating the block "out" and then when you go from 1 back to 0, you're applying the same interpolation in reverse
so this code appears to work correctly for me:
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current;
const [toggled, setToggled] = React.useState(false);
useEffect(() => {
Animated.spring(value, {
toValue: toggled ? 0 : 1,
friction: 10,
useNativeDriver: false,
}).start();
}, [toggled, value]);
return (
<>
{cards.map((card, index) => {
const rotate = (index - 1) * alpha;
return (
<Animated.View
key={card}
style={{
transform: [
{ translateY: -50 },
{ translateX: -100 },
{
rotate: value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`],
}),
},
{ translateX: 100 },
],
borderRadius: 15,
position: 'absolute',
backgroundColor: card,
height: 100,
width: 200,
}}
/>
);
})}
<View style={{ paddingTop: 100 }}>
<TouchableOpacity
onPress={() => {
setToggled(!toggled);
}}>
<Text style={{ fontSize: 30 }}> Animate </Text>
</TouchableOpacity>
</View>
</>
);
};

Related

Combined total for multiple slider react native with reanmiated

I need to do the following implementation in React Native:
http://jsfiddle.net/t8mzLuh4/3/
But I can't think of the most optimal way to do it, I have tried different ways, but they have not been optimal.
I am trying to implement a screen where there are 3 sliders, and I want to make the combined total of the 3 sliders never go over the maximum.
I don't care how it is implemented, it can be from a starting 0, and as soon as I change 1 slider, the remaining total available decreases or putting a slider beyond the maximum, decreases the values on the other sliders.
This is my current implementation of the slider with reanimated 2
const Slider = ({
label,
min,
value,
labelAmount,
colorAmount,
max,
currency,
onChange,
step,
}: SliderProps) => {
const isActive = useSharedValue(false);
const translationX = useSharedValue(((width - PADDING * 2) * 0) / max);
const amount = useSharedValue(value);
const formattedAmount = useSharedValue(
formatAsMoney(value, currency ?? '$')
);
const wrapper = (value: number) => {
formattedAmount.value = formatAsMoney(value, currency ?? '$');
};
useDerivedValue(() => {
runOnJS(wrapper)(amount.value);
});
const onSelect = (value: number) => {
'worklet';
runOnJS(onChange)(value);
};
const onGestureEvent = useAnimatedGestureHandler({
onStart: () => {
isActive.value = true;
},
onActive: (event) => {
translationX.value = interpolate(
event.x,
[0, width],
[0 - RADIUS, width - PADDING],
Extrapolate.CLAMP
);
amount.value =
Math.ceil(
interpolate(
translationX.value,
[0 - RADIUS, width - PADDING * 2 + RADIUS],
[min, max],
Extrapolate.CLAMP
) / step
) * step;
},
onEnd: () => {
isActive.value = false;
onSelect(amount.value);
},
});
const transform = useAnimatedStyle(() => {
const translateX = translationX.value;
return {
transform: [
{ translateX },
{ scale: withSpring(isActive.value ? 1 : 0.75) },
],
};
});
const barWidth = useAnimatedStyle(() => {
return {
width: translationX.value + RADIUS,
};
});
return (
<View>
<View
style={{
flexDirection: "row",
justifyContent="space-between",
alignItems="center"
}}
>
<Text>
{label}
</Text>
<ReText
text={formattedAmount}
style={[
styles.text,
{
fontSize: labelAmount === 'small' ? 18 : 22,
color: '#000',
},
]}
/>
</View>
<View style={styles.shadow}>
<View style={styles.container}>
<View style={styles.barContainer}>
<View
style={[
styles.bar,
{
backgroundColor: '#fff',
},
]}
/>
<Animated.View
style={[
styles.bar,
{
backgroundColor: 'green',
},
barWidth,
]}
/>
</View>
<PanGestureHandler {...{ onGestureEvent }}>
<Animated.View
style={[
StyleSheet.absoluteFillObject,
{
marginHorizontal: PADDING / 2,
},
]}
>
<Animated.View
style={[styles.cursor, transform, styles.shadow]}
/>
</Animated.View>
</PanGestureHandler>
</View>
<View
style={{
alignSelf: "flex-start",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%"
}}
>
<Text >
{formatAsMoney(min, currency ?? '$')}
</Text>
<Text>
{formatAsMoney(max, currency ?? '$')}
</Text>
</View>
</View>
</View>
);
};
const useStyles = StyleSheet.create({
shadow: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
alignItems: 'center',
},
container: {
height: HEIGHT,
width: width,
alignItems: 'center',
justifyContent: 'center',
},
barContainer: {
height: BAR_HEIGHT,
width: width - PADDING,
},
bar: {
borderRadius: BORDER_RADIUS,
...StyleSheet.absoluteFillObject,
},
cursor: {
height: RADIUS * 2,
width: RADIUS * 2,
borderRadius: RADIUS,
backgroundColor: 'white',
},
text: {
fontWeight: '700',
},

Close bottom sheet modal only from top part in React Native

I created this bottom sheet modal component and it works just fine, the thing is that when it has a ScrollView inside, the scroll no longer works properly because it tries to close the modal. Is there any way to make the modal only be closed from the top part and the rest of the content be scrolled as normal?
export default (props: any) => {
const screenHeight = Dimensions.get('screen').height;
const panY = useRef(new Animated.Value(screenHeight)).current;
const resetPositionAnim = Animated.timing(panY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
});
const closeAnim = Animated.timing(panY, {
toValue: screenHeight,
duration: 300,
useNativeDriver: true,
});
const translateY = panY.interpolate({
inputRange: [-1, 0, 1],
outputRange: [0, 0, 1],
});
const handleDismiss = () => closeAnim.start(props.onDismiss);
useEffect(() => {
resetPositionAnim.start();
}, [resetPositionAnim]);
const panResponders = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => false,
onPanResponderMove: Animated.event([null, { dy: panY }], {
useNativeDriver: false,
}),
onPanResponderRelease: (_, gs) => {
if (gs.dy > 0 && gs.vy > 2) {
return handleDismiss();
}
return resetPositionAnim.start();
},
}),
).current;
const insets = useSafeAreaInsets();
return (
<Modal animated animationType="fade" visible={props.visible} transparent onRequestClose={handleDismiss}>
<TouchableWithoutFeedback onPress={handleDismiss}>
<View style={styles.overlay}>
<Animated.View
style={{
...styles.container,
maxHeight: screenHeight - (insets.top || 100),
paddingBottom: insets.bottom,
transform: [{ translateY: translateY }],
}}
{...panResponders.panHandlers}
>
<View style={styles.sliderIndicatorRow}>
<View style={styles.sliderIndicator} />
</View>
<View style={[styles.sliderIndicatorRow, { justifyContent: 'flex-end', marginRight: 20 }]}>
<TouchableOpacity onPress={handleDismiss}>
<Feather name="x" size={16} color={colors.primaryText} />
</TouchableOpacity>
</View>
{props.children}
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
backgroundColor: 'rgba(0,0,0,0.2)',
flex: 1,
justifyContent: 'flex-end',
},
container: {
backgroundColor: 'white',
paddingTop: 12,
borderTopRightRadius: 30,
borderTopLeftRadius: 30,
minHeight: 200,
},
sliderIndicatorRow: {
flexDirection: 'row',
marginBottom: 4,
alignItems: 'center',
justifyContent: 'center',
},
sliderIndicator: {
backgroundColor: '#C4C4C4',
height: 6,
width: 75,
borderRadius: 100,
},
});

Want to create a flip card, but it won't flip

I'm trying to create a Flip card, a simple one, but when I press to flip it won't, got the example from https://www.youtube.com/watch?v=ghLuM0ANOk4.
When I click the Flip button nothing happens, not even an error message
Here is the code
const [isFlipped, setIsFlipped] = useState(false);
const animate = useRef(new Animated.Value(0));
const frontRef = useRef();
const backRef = useRef();
const doAFlip = () => {
Animated.timing(animate.current, {
duration: 300,
toValue: isFlipped ? 0 : 180,
useNativeDriver: true,
}).start(() => {
setIsFlipped(!isFlipped);
});
};
const interpolateFront = animate.current.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
});
const interpolateBack = animate.current.interpolate({
inputRange: [0, 180],
outputRange: [ '180deg', '360deg'],
});
const rotateFront = {
transform: [
{
rotateY: interpolateFront,
},
],
};
const rotateBack = {
transform: [
{
rotateY: interpolateBack,
},
],
};
and here
<View>
<Animated.View style={[rotateFront], {backfaceVisibility: 'hidden'}}>
<View style={{backgroundColor: 'green', height: 60, width: 60}}/>
</Animated.View>
<Animated.View style={[rotateBack], {backfaceVisibility: 'hidden', position: 'absolute', top: 0}}>
<View style={{backgroundColor: 'blue', height: 60, width: 60}}/>
</Animated.View>
<Button title='Flip' onPress={doAFlip}>Flip</Button>
</View>````

Why all the Icons of my custom bottom tab bar navigator are moving at the same time?

I am trying to create a CustomBottomTabNavigator in react native. By now I have applied the linear gradient and added the icons on top of the tab. My goal is to move the icon upwards when the focus is on it, but for some reason, all the icons are moving upwards when the focus is on only one icon.
Here is the code:
import React, { useRef } from "react";
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
} from "react-native";
import * as Icons from "#expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
const CustomTabBar = ({ state, descriptors, navigation }) => {
let icons_name = ["home", "search", "tv", "user"];
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<LinearGradient
colors={["#181823", "#3A3A46", "#3A3A46"]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
locations={[0.2, 0.6, 0.3]}
style={styles.container}
>
<View style={styles.tabs}>
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return (
<Animated.View
key={index}
style={{
flex: 1,
flexDirection: "row",
transform: [{ translateY }],
}}
>
<Icons.Feather
name={icons_name[`${index}`]}
size={24}
color="#fff"
onPress={() => animationHome(isFocused, route.name)}
/>
</Animated.View>
);
})}
</View>
</LinearGradient>
);
};
export default CustomTabBar;
const styles = StyleSheet.create({
container: {
position: "absolute",
height: 40,
bottom: 20,
right: 30,
left: 20,
elevation: 2,
borderRadius: 20,
},
tabs: {
flex: 1,
flexDirection: "row",
alignItems: "center",
marginLeft: 48,
},
});
Here is the gif of the animation that is happening
Gif. I am using animated API from react-native to achieve this animation.
Let each of the child components have their own animation values.
// In the parent component
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return <Child isFocused={isFocused} />;
})}
// Then for each child
const Child = ({ isFocused }) => {
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<Animated.View
style={{
transform: [{ translateY }],
}}
>
<Icons.Feather onPress={() => animationHome(isFocused, route.name)}/>
</Animated.View>
);
}

Invariant Violation: invariant violation: outputRange cannot include #ffffff. (#ffffff, #aaa2d7,#aaa2d7)

I get this error even I change: inputIndex === i ? 255: 0 to inputIndex === i ? "#ffffff" : "#aaa2d7".
the purpose, is when the first tab is active the title color should be white "#ffffff" and others tabs should be "#aaa2d7"
const inputRange = props.navigationState.routes.map((x, i) => i);
return (
<View style={{backgroundColor: "#5243af",flexDirection: 'row', height: Metrics.HEIGHT * 0.1, elevation: 0}}>
{ props.navigationState.routes.map((route, i) => {
const color = Animated.color(
Animated.round(
Animated.interpolate(props.position, {
inputRange,
outputRange: inputRange.map(inputIndex =>
inputIndex === i ? "#ffffff" : "#aaa2d7"
),
})
),
0,
0
);
return (
<TouchableOpacity
style={{flex: 1, alignItems: 'center', borderLeftColor: "#9a91d2", borderLeftWidth: 1/* , borderBottomWidth: 3, borderBottomColor "red" */}}
key={i}
onPress={() => {
//this.changeTabs(route.key)
this.setState({ index: i })
}}>
{this._getTabBarIcon(route.key)}
<Animated.Text style={{ color, fontSize: Fonts.moderateScale(15), marginBottom: 10 }}>{route.title.toLocaleUpperCase()}</Animated.Text>
</TouchableOpacity>
);
})}
</View>
);
}
thank you
You can try something like this.
const scrollY = new Animated.Value(0);
const bgColor = Animated.interpolate(scrollY, {
inputRange: [0, HEADER_HEIGHT],
outputRange: [Animated.color(56, 180, 113, 0), Animated.color(56, 180, 113, 1)]
})