Related
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',
},
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,
},
});
I have a component with a ScrollView that works, and I want to add a scenario where for an example I want to fit all content on the screen width, and for other scenarios I want to scroll through them. How Can I achieve that ?
Here is my current Implementation:
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.container}
contentContainerStyle={styles.containerContainer}
>
<View>
<View style={styles.tabs}>
{Object.keys(tabs).map((item, index) => {
const isSelected = item === selectedTab;
return (
<Tab
key={item}
item={tabs[item]}
select={() => select(index, item)}
setMeasures={(width) => setWidth(width, index)}
isSelected={isSelected}
gap={gap}
/>
);
})}
</View>
<Animated.View
style={[
styles.indicator,
{
transform: [{ translateX: value }],
},
measures[selectedIndex]
? { width: measures[selectedIndex] - gap }
: null,
]}
/>
</View>
</ScrollView>
and here is the stylings
indicator: {
backgroundColor: BURNING_ORANGE,
height: hp(2),
width: 0,
},
label: {
alignItems: 'center',
color: CLOUD_BURST,
fontFamily: CIRCULARMEDIUM,
marginVertical: hp(10),
},
selectedLabel: {
alignItems: 'center',
color: BURNING_ORANGE,
fontFamily: CIRCULARMEDIUM,
marginVertical: hp(10),
},
tab: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
paddingEnd: wp(20),
},
tabs: {
flexDirection: 'row',
},
Also, If I added flex: 1 to contentContainerStyle of my ScrollView the View just dissappear
I just wonder how can I hide both Header and Footer at the same time and with the same animated value?
Becouse I think I cannot use the same animated value for the animate multiple components at the same time.
MY COMPONENT
<SafeAreaView style={{ flex: 1 }}>
<Animated.View
style={{
transform: [{ translateY }],
position: "absolute",
top: 0,
left: 0,
zIndex: 100,
width: "100%",
}}
>
<MainHeader logo="socialLogo" navigation={navigation} />
</Animated.View>
<Animated.FlatList
ref={ref}
onEndReachedThreshold={0.5}
contentContainerStyle={{ paddingTop: HEADER_HEIGHT }}
bounces={false}
onScroll={handleScroll}
nestedScrollEnabled={true}
ListHeaderComponent={<Stories data={data} app={app} />}
pinchGestureEnabled={false}
ListEmptyComponent={<FeedItemLazy />}
onMomentumScrollEnd={handleSnap}
scrollEventThrottle={16}
renderScrollComponent={(props) => <ScrollView {...props} />}
showsVerticalScrollIndicator={false}
maxToRenderPerBatch={10}
onEndReached={() => {
props.social.getFeeds.executeNext({ lastID: lastId });
}}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
/>
}
data={data}
renderItem={({ item, index }) => (
<>
<FeedItem
data={item}
app={app}
navigation={navigation}
social={social}
/>
<Separator height={2} />
</>
)}
keyExtractor={(item) => item._id}
/>
{social.getFeeds.isExecuting && (
<LottieView
source={require("../../../../assets/animation/16337-banana-loader.json")}
loop
autoPlay
style={{ width: 50, height: 50 }}
/>
)}
<ModalSelector navigation={navigation} />
</SafeAreaView>
This is a Feeds component. Users feeds being listed in the Flatlist, and my goal is when user scrolls down make the MainHeader and Bottom tabs collapsable. I think hardest part is to make bottom tabs unvisible, becose they are coming directly from react-navigation v5. from createBottomTabNavigator. I dont know how can I transfer the translateY value to tab navigator.
import React from 'react';
import {
Animated,
Easing,
Button,
View,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
const App = () => {
const animatedValue = React.useRef(new Animated.Value(0)).current;
const [hidden, setHidden] = React.useState(false);
const startAnimation = (toValue) => {
Animated.timing(animatedValue, {
toValue: hidden ? 0 : 1,
duration: 700,
easing: Easing.linear,
useNativeDriver: true,
}).start(() => setHidden(!hidden));
};
const translateY = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -50],
extrapolate: 'clamp',
});
return (
<View style={{ flex: 1 }}>
<Animated.View
style={[styles.item, { top: 0, transform: [{ translateY }] }]}
/>
<TouchableOpacity
style={styles.button}
onPress={startAnimation.bind(null, 1 - animatedValue.__value)}>
<Text>Hide</Text>
</TouchableOpacity>
<Animated.View
style={[
styles.item,
{
bottom: 0,
transform: [{ translateY: Animated.multiply(translateY, -1) }],
},
]}
/>
</View>
);
};
const styles = StyleSheet.create({
item: {
width: '100%',
height: 50,
position: 'absolute',
backgroundColor: 'red',
},
button: {
width: '50%',
height: 50,
backgroundColor: 'pink',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
borderRadius: 25,
position: 'absolute',
top: 200,
},
});
export default App;
Live Demo.
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>