如何用Reanimated和HugeIcons实现图标触摸时平滑颜色切换
解决方案
你的问题在于直接通过isFocused切换颜色是瞬间变化,没有利用Reanimated的动画能力,且全局iconColor变量未和每个tab的状态关联。下面是修改后的代码,实现图标颜色从黑到白的平滑过渡:
关键修改点
- 基于
activeIndex为每个tab创建颜色插值动画,无需单独维护每个tab的状态 - 使用
interpolateColor实现RGB颜色的平滑过渡 - 将
HugeiconsIcon包装为Animated组件,支持动画化的color属性
import Animated, { useSharedValue, useAnimatedStyle, withTiming, interpolate, interpolateColor, useAnimatedProps, createAnimatedComponent, } from 'react-native-reanimated'; import { BaseButton } from '@react-native-material/core'; import { BottomTabBarProps } from '@react-navigation/bottom-tabs'; import { Home05Icon, WaterfallUp01Icon, Settings01Icon, HugeiconsIcon } from '@hugeicons/react-native'; // 将HugeiconsIcon转为支持动画的组件 const AnimatedHugeiconsIcon = createAnimatedComponent(HugeiconsIcon); const SINGLE_TAB_WIDTH = 100; // 替换为你的单个tab实际宽度 function MyTabBar({ state, descriptors, navigation }: BottomTabBarProps) { const activeIndex = useSharedValue(state.index); React.useEffect(() => { activeIndex.value = withTiming(state.index, { duration: 200 }); }, [activeIndex, state.index]); const slideAnimatedStyle = useAnimatedStyle(() => { return { transform: [{ translateX: activeIndex.value * SINGLE_TAB_WIDTH }], }; }); return ( <View style={styles.container}> <Animated.View style={[ styles.slide, slideAnimatedStyle, ]} /> {state.routes.map((route, index) => { const { options } = descriptors[route.key]; let iconName; if (route.name === 'HomeScreen') { iconName = Home05Icon; } else if (route.name === 'StatScreen') { iconName = WaterfallUp01Icon; } else if (route.name === 'SettingScreen') { iconName = Settings01Icon; } // 为当前tab生成动画化的颜色属性 const animatedProps = useAnimatedProps(() => { // 计算当前tab与激活索引的差值,生成0-1的动画进度 const progress = interpolate(activeIndex.value, [index - 0.5, index, index + 0.5], [0, 1, 0]); return { color: interpolateColor(progress, [0, 1], ['#000000', '#FFFFFF']), }; }); const onPress = () => { const event = navigation.emit({ type: 'tabPress', target: route.key, canPreventDefault: true, }); if (!state.index === index && !event.defaultPrevented) { navigation.navigate(route.name); } }; return ( <BaseButton key={index} onPress={onPress} rippleColor={'transparent'} style={{ width: SINGLE_TAB_WIDTH, alignItems: 'center', zIndex: 2 }} > <AnimatedHugeiconsIcon icon={iconName} size={22} animatedProps={animatedProps} /> </BaseButton> ); })} </View> ); } // 自定义样式示例 const styles = { container: { flexDirection: 'row', backgroundColor: '#ffffff', borderTopWidth: 1, borderTopColor: '#eeeeee', height: 60, alignItems: 'center', }, slide: { position: 'absolute', width: SINGLE_TAB_WIDTH, height: '100%', backgroundColor: '#165DFF', // 替换为你的tab激活背景色 zIndex: 1, }, };
代码解释
- 动画组件包装:通过
createAnimatedComponent将普通的HugeiconsIcon转为支持动画属性的组件,让color属性可以被Reanimated驱动。 - 颜色插值计算:在
useAnimatedProps中,根据activeIndex的平滑变化,计算当前tab的动画进度,再用interpolateColor将进度映射为从黑色到白色的渐变颜色。 - 导航状态联动:当切换tab时,
activeIndex通过withTiming做平滑过渡,自动带动所有tab的颜色动画同步执行。
修改后,切换tab时图标颜色会从黑色平滑过渡到白色,同时保留你原本的滑动指示器动画效果。
内容的提问来源于stack exchange,提问作者Thuận Dương Minh
相关产品推荐
相关产品推荐

