Reputation: 1
I used a bottom tab bar from a youtube tutorial but its really slow between the time I click on a tab and the time its start moving and going to the other tab It take like 1 seconds between the moment I click on the tab and the moment the animation start. Do you have an idea of what could be the issue ? I think the interpolate path is not optimise but i'm a beginners on expo and animation here is what the tab bar looks like And the code is :
import React, { FC, useEffect } from "react";
import { StyleSheet, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import Animated, {
runOnJS,
useAnimatedProps,
useSharedValue,
withTiming,
Easing,
} from "react-native-reanimated";
import { interpolatePath } from "react-native-redash";
import { useRouter, usePathname } from "expo-router";
import { SCREEN_WIDTH } from "@/src/constants/screen";
import usePath from "@/src/hooks/usePath";
import { getPathXCenter } from "@/src/utils/path";
import TabItem from "./tabItem";
import AnimatedCircle from "./animatedCircle";
import HeadphoneIcon from "@/src/components/icons/headphoneIcon";
import HomeIcon from "@/src/components/icons/homeIcon";
import HistoryIcon from "@/src/components/icons/historyIcon";
import ProfileIcon from "@/src/components/icons/profileIcon";
import SettingsIcon from "../icons/settingsIcon";
const AnimatedPath = Animated.createAnimatedComponent(Path);
const TABS = [
{ name: "home", keyword: "home", label: "Home", icon: HomeIcon },
{ name: "playlistsList", keyword: "playlist", label: "Playlist", icon: HeadphoneIcon },
{ name: "history", keyword: "history", label: "History", icon: HistoryIcon },
{ name: "profile", keyword: "profile", label: "Profile", icon: ProfileIcon },
{ name: "settings", keyword: "settings", label: "Settings", icon: SettingsIcon },
];
interface CustomBottomTabProps {
state: {
index: number;
routes: Array<{ name: string }>;
};
navigation: {
navigate: (name: string) => void;
};
}
export const CustomBottomTab: FC<CustomBottomTabProps> = ({ state, navigation }) => {
const { containerPath, curvedPaths, tHeight } = usePath();
const circleXCoordinate = useSharedValue(0);
const progress = useSharedValue(1);
const router = useRouter();
const pathname = usePathname();
const currentIndex = TABS.findIndex(tab => {
if (tab.keyword === 'home' && pathname === '/') {
return true;
}
return pathname.includes(tab.keyword);
});
const handleMoveCircle = (currentPath: string) => {
circleXCoordinate.value = getPathXCenter(currentPath);
};
const selectIcon = (routeName: string, isActive: boolean) => {
const color = isActive ? "#000000" : "#BABABA";
const IconComponent = TABS.find(tab => tab.name === routeName)?.icon;
return IconComponent ? <IconComponent color={color} /> : null;
};
useEffect(() => {
if (currentIndex >= 0) {
progress.value = withTiming(currentIndex + 1, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});
}
}, [pathname]);
const animatedProps = useAnimatedProps(() => {
console.log('progress.value', progress.value);
const currentPath = interpolatePath(
progress.value,
Array.from({ length: curvedPaths.length }, (_, index) => index + 1),
curvedPaths,
);
runOnJS(handleMoveCircle)(currentPath);
return {
d: `${containerPath} ${currentPath}`,
};
});
const handleTabPress = (index: number, path: string) => {
console.log('press', index);
if (path === "home") {
router.push(`/(auth)/(tabs)/`);
} else {
router.push(`/(auth)/(tabs)/${path}`);
}
progress.value = withTiming(index, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});
};
return (
<View style={styles.tabBarContainer}>
<Svg width={SCREEN_WIDTH} height={tHeight} style={styles.shadowMd}>
<AnimatedPath fill={"white"} animatedProps={animatedProps} />
</Svg>
<AnimatedCircle circleX={circleXCoordinate} />
<View
style={[
styles.tabItemsContainer,
{
height: tHeight,
},
]}
>
{TABS.map((tab, index) => (
<TabItem
key={index.toString()}
label={tab.label}
icon={selectIcon(tab.name, currentIndex === index)}
activeIndex={currentIndex + 1}
index={index}
onTabPress={() => handleTabPress(index + 1, tab.name)}
/>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
tabBarContainer: {
position: "absolute",
bottom: 0,
zIndex: 2,
},
tabItemsContainer: {
position: "absolute",
flexDirection: "row",
width: "100%",
},
shadowMd: {
elevation: 3,
shadowColor: "#000",
shadowOpacity: 0.2,
shadowRadius: 3,
shadowOffset: { width: 0, height: 3 },
}
});
here is usePath
import { useMemo } from "react";
import { curveBasis, line } from "d3-shape";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { SCREEN_WIDTH } from "@/src/constants/screen";
import { parse } from "react-native-redash";
type GenerateTabShapePath = (
position: number,
adjustedHeight: number,
) => string;
const NUM_TABS = 5;
const SCALE = 0.6;
const TAB_BAR_HEIGHT = 50;
const generateTabShapePath: GenerateTabShapePath = (
position,
adjustedHeight,
) => {
const adjustedWidth = SCREEN_WIDTH / NUM_TABS;
const tabX = adjustedWidth * position;
const lineGenerator = line().curve(curveBasis);
const tab = lineGenerator([
[tabX - 100 * SCALE, 0],
[tabX - (65 + 35) * SCALE, 0],
[tabX - (50 - 10) * SCALE, -4 * SCALE],
[tabX - (50 - 10) * SCALE, (adjustedHeight - 14) * SCALE],
[tabX + (50 - 15) * SCALE, (adjustedHeight - 14) * SCALE],
[tabX + (50 - 10) * SCALE, -4 * SCALE],
[tabX + (65 + 35) * SCALE, 0],
[tabX + 100 * SCALE, 0],
]);
return `${tab}`;
};
const usePath = () => {
const insets = useSafeAreaInsets();
const tHeight = TAB_BAR_HEIGHT + insets.bottom;
const adjustedHeight = tHeight - insets.bottom;
const containerPath = useMemo(() => {
return `M0,0L${SCREEN_WIDTH},0L${SCREEN_WIDTH},0L${SCREEN_WIDTH},${tHeight}L0,${tHeight}L0,0`;
}, [tHeight]);
const curvedPaths = useMemo(() => {
return Array.from({ length: NUM_TABS }, (_, index) => {
const tabShapePath = generateTabShapePath(index + 0.5, adjustedHeight);
return parse(`${tabShapePath}`);
});
}, [adjustedHeight]);
return { containerPath, curvedPaths, tHeight };
};
export default usePath;
Upvotes: 0
Views: 33