想要实现的效果:
动画我们用lottie-react-native来实现:github项目地址
lottie-react-native的使用方法不赘述了,具体可以参考官网文档
这里主要说一下React-Navigation createBottomTabNavigator中每个StackNavigator里tabBarIcon的使用,主要是这里有个坑,也就是tabBarIcon里的focused有问题,每次点击回调均为
true,false打印两次
tabBarIcon: ({ focused, tintColor }) => {
console.log('focused=====>',focused)
return <TabBarIcon focused={focused} tintColor={tintColor} index={3} />
}
我们来查看一下源码:
1.路径:node_modules\react-navigation\src\react-navigation.js
<--line:116-118-->
get createBottomTabNavigator() {
return require('react-navigation-tabs').createBottomTabNavigator;
},
2.路径:node_modules\react-navigation-tabs\src\navigators\createBottomTabNavigator.js
<--line:131-->
this._renderTabBar() 找到BottomTabBar
3.路径:node_modules\react-navigation-tabs\src\views\BottomTabBar.js
<--line:365-->
this._renderIcon(scene)
找到scene的定义 const scene = { route, focused };
打印log 查看focused的值
到这里我们发现focused的值是正确打印的,那么是哪里出问题了呢,我们继续往下找:
在_renderIcon 查看返回的组件CrossFadeIcon
路径:node_modules\react-navigation-tabs\src\views\CrossFadeIcon.js
然后我们发现在render方法里面有两行注释:
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.
大概意思就是这个组件以绝对布局的方式返回了两个icon,一个代表激活也就是选中的icon,一个代表未激活也就是未选中的icon
而且我们查看源码:
render() {
const {
route,
activeOpacity,
inactiveOpacity,
activeTintColor,
inactiveTintColor,
renderIcon,
horizontal,
style,
} = this.props;
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.
return (
<View style={style}>
<Animated.View style={[styles.icon, { opacity: activeOpacity }]}>
{renderIcon({
route,
focused: true,
horizontal,
tintColor: activeTintColor,
})}
</Animated.View>
<Animated.View style={[styles.icon, { opacity: inactiveOpacity }]}>
{renderIcon({
route,
focused: false,
horizontal,
tintColor: inactiveTintColor,
})}
</Animated.View>
</View>
);
}
没错,renderIcon props返回的正是我们发现问题的地方-focused返回了一个true和一个false
发现问题我们就好办了,注释掉一个icon,然后将上一个组件的focused字段传过来,搞定
修改之后的代码:
render() {
const {
route,
activeOpacity,
inactiveOpacity,
activeTintColor,
inactiveTintColor,
renderIcon,
horizontal,
style,
focused
} = this.props;
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.
return (
<View style={style}>
<Animated.View style={[styles.icon]}>
{renderIcon({
route,
focused: focused,
horizontal,
tintColor: focused ? activeTintColor : inactiveTintColor,
})}
</Animated.View>
{/* <Animated.View style={[styles.icon, { opacity: inactiveOpacity }]}>
{renderIcon({
route,
focused: false,
horizontal,
tintColor: inactiveTintColor,
})}
</Animated.View> */}
</View>
);
}
当然上一个组件也需要传focused={focused}过来
修改之后我们在最开始的地方tabBarIcon里面的focused参数就正常了
有了正确的focused之后就好办了
在自定义的icon组件中传过去focused字段,当focused为true时为LottieView设置启动动画
相反则停止动画
this.animation.play()
和
this.animation.reset()
还有需要注意的就是当进入主页的时候第一个icon默认选中,也就是动画的最后一帧
只需要判断位置设置progress字段即可