This is BottomTabNavigator.js file:
const BottomTab = createBottomTabNavigator();
const { height } = Dimensions.get('window');
const SNAP_TOP = height + StatusBar.currentHeight;
const SNAP_BOTTOM = 54 + 50;
const BottomTabNavigator = () => {
const yValue = useSharedValue(SNAP_BOTTOM);
return (
<>
{/* this component contain all screen and bottom tab navigator */}
<BottomTab.Navigator
tabBar={(props) => <BottomTabBar {...props} yValue={yValue} snapTop={SNAP_TOP} snapBottom={SNAP_BOTTOM} />}
initialRouteName="Home" // "Home" initial screen to render
backBehavior="history"
screenOptions={{
headerShown: false,
tabBarHideOnKeyboard: true,
}}
>
<BottomTab.Screen
name="Home"
component={Home}
options={{
tabBarLabel: "Home",
// we are importing focused as well as size from BottomTabBar.js file
tabBarIcon: ({ color }) => <HomeIcon height={icon_size} width={icon_size} fill={color} />,
}}
/>
<BottomTab.Screen
name="Search"
component={Search}
options={{
tabBarLabel: "Search",
tabBarIcon: ({ color }) => <SearchIcon height={icon_size} width={icon_size} fill={color} />
}}
/>
</BottomTab.Navigator>
{/* this component contains the mini player & player page */}
<SlidingBottom yValue={yValue} snapTop={SNAP_TOP} snapBottom={SNAP_BOTTOM} />
</>
);
}
export default BottomTabNavigator;
This is SlidingBottom.js file:
const { height } = Dimensions.get('window');
const SNAP_TOP = height + StatusBar.currentHeight;
const SNAP_BOTTOM = 54 + 50;
const config = {
damping: 15,
mass: 1,
stiffness: 120,
restSpeedThreshold: 0.1,
restDisplacementThreshold: 0.1,
}
const SlidingBottom = ({ yValue }) => {
// const yValue = useSharedValue(SNAP_BOTTOM);
const prevYValue = useSharedValue(SNAP_BOTTOM);
const pan = Gesture.Pan().minDistance(1).onBegin(() => {
prevYValue.value = yValue.value;
}).onUpdate((evevnt) => {
yValue.value = clamp(
prevYValue.value - evevnt.translationY,
SNAP_BOTTOM,
SNAP_TOP
);
}).onEnd((evevnt) => {
yValue.value = withSpring(
snapPoints(
clamp(
prevYValue.value - evevnt.translationY,
SNAP_BOTTOM,
SNAP_TOP
),
-evevnt.velocityY,
[SNAP_BOTTOM, SNAP_TOP]),
config
);
}).runOnJS(true);
const yStyle = useAnimatedStyle(() => ({
height: yValue.value
}))
const oStyle = useAnimatedStyle(() => ({
opacity: interpolate(yValue.value, [SNAP_TOP, SNAP_BOTTOM], [0, 1], Extrapolation.CLAMP)
}))
return (
<GestureDetector gesture={pan}>
<Animated.View style={[yStyle, { position: "absolute", left: 0, right: 0, bottom: 0, backgroundColor: "red", borderColor: "purple", borderWidth: 1, zIndex: layout_schema.zIndexForSlidingBottom }]}>
<Animated.View style={[oStyle, { height: 50, backgroundColor: "mediumseagreen" }]} />
<Player />
</Animated.View>
</GestureDetector>
);
}
export default SlidingBottom;
This is BottomTabBar.js file:
import { useEffect, useState, useMemo } from "react";
import { Dimensions, Keyboard, Text, View, TouchableOpacity, StatusBar, StyleSheet } from "react-native";
import { useTheme } from "@react-navigation/native";
import Animated, { Extrapolation, interpolate, useSharedValue, useAnimatedStyle, withTiming, Easing } from "react-native-reanimated";
// import constants
import { color_schema, icon_size, layout_schema } from "../../constants/constants";
const { height } = Dimensions.get('window');
const BottomTabBar = ({ state, descriptors, navigation, yValue, snapTop, snapBottom }) => {
// using this hook we can get all colors used by react navigation
// as hooks can be only called inside function that's why we cannot declare all styles in StyleSheet
const { colors } = useTheme();
// this is used to check if keyboard is visible or not
const [keyboardVisible, setKeyboardVisible] = useState(true);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", () => {
//Whenever keyboard did show make it don't visible
setKeyboardVisible(false);
});
const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
setKeyboardVisible(true);
});
return () => {
keyboardDidShowListener.remove();
keyboardDidHideListener.remove();
};
}, [])
const bStyle = useAnimatedStyle(() => ({
// height: interpolate(yValue.value, [snapTop, snapBottom], [0, 54], Extrapolation.CLAMP), then add bottom: 0 in styles.bottom
bottom: interpolate(yValue.value, [snapTop, snapBottom], [-54, 0], Extrapolation.CLAMP)
}));
return (
keyboardVisible &&
<Animated.View style={[styles.bottom, bStyle, { backgroundColor: colors.card }]}>
{
state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label = options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = useMemo(() => state.index === index, [state.index]);
{/* function for onPress event */ }
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
{/* function for onLongPress event */ }
const onLongPress = () => {
const event = navigation.emit({
type: "tabLongPress",
target: route.key,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
{/* setting color for icon and text */ }
const color = isFocused ? color_schema.focused_header_color : color_schema.unfocused_header_color
const widthSharedValue = useSharedValue(isFocused ? "100%" : "0%");
useEffect(() => {
widthSharedValue.value = withTiming(isFocused ? "100%" : "0%", {
duration: 300,
easing: Easing.bounce,
})
}, [state.index])
const animatedWidth = useAnimatedStyle(() => {
'worklet';
return {
width: widthSharedValue.value,
};
})
return (
<TouchableOpacity
key={route.key} // using route.key for unique keys but we can use index also
// accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
>
<View style={styles.navItem}>
{/* this view contains icon */}
<View>
{options.tabBarIcon({ color, focused: isFocused, size: icon_size })}
</View>
{/* this view contains label */}
<View style={styles.labelContainer}>
<Text style={[styles.label, { color }]}>{label}</Text>
</View>
{/* this view contains undedrline */}
<Animated.View style={[styles.underline, animatedWidth]} />
</View>
</TouchableOpacity>
);
})
}
</Animated.View>
);
}
const styles = StyleSheet.create({
bottom: {
borderTopColor: color_schema.border_bottom_color,
borderTopWidth: 1,
flexDirection: "row",
justifyContent: "space-evenly",
alignItems: "center",
paddingTop: 5,
paddingBottom: 2,
position: "absolute",
left: 0,
right: 0,
zIndex: layout_schema.zIndexForBottomTabNavigator,
height: 54,
overflow: "hidden",
},
navItem: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: "100%",
},
labelContainer: {
marginTop: -1,
marginBottom: 1,
marginHorizontal: "auto",
width: "auto",
},
label: {
fontSize: 10,
fontWeight: "400",
},
underline: {
backgroundColor: color_schema.focused_header_color,
borderRadius: 2,
height: 2,
},
});
export default BottomTabBar;
This code works fine in RN 0.74, but not in RN 0.76. In RN 0.76, height is not changing on initial render, but when I click on some other tab, it works fine. I tried to debug but cannot find the reason.
Also, the reason I want to jump to RN 0.76 it due to its new architecture.
I know there are other npm packages like this, but I am skeptical in using those pacakges.
Kindly help me react-native community.