如何在反应本机中创建动画标签栏?



我从dribble看到了这个动画标签栏,我很想学习如何构建它。 标签栏动画

这就是你要找的

需要两个库 react-native-svg 和 d3-shape 来绘制弧形

完整代码


import React, {Component} from 'react';
import {
View,
Dimensions,
SafeAreaView,
StyleSheet,
Animated,
TouchableWithoutFeedback,
} from 'react-native';
import Svg, {Path} from 'react-native-svg';
import * as shape from 'd3-shape';
const {width} = Dimensions.get('window');
const height = 80;
const tabs = [
{
name:
'https://www.clipartmax.com/png/full/255-2556971_computer-icons-user-management-clip-art-default-profile-picture-green.png',
},
{
name: 'https://img.icons8.com/material/4ac144/256/user-male.png',
},
{
name: 'https://img.icons8.com/material/4ac144/256/camera.png',
},
{
name:
'https://images.vexels.com/media/users/3/154655/isolated/preview/71dccbb077597dea55dfc5b7a7af52c4-location-pin-contact-icon-by-vexels.png',
},
{
name:
'https://img.pngio.com/corona-icono-vectorial-gratis-diseado-por-freepik-ingredientes-corona-png-gratis-1200_630.png',
},
];
const tabWidth = width / tabs.length;
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
const left = shape
.line()
.x((d) => d.x)
.y((d) => d.y)([
{x: 0, y: 0},
{x: width, y: 0},
]);
const tab1 = shape
.line()
.x((d) => d.x)
.y((d) => d.y)
.curve(shape.curveBasis)([
{x: width - 10, y: 0},
{x: width + 5, y: 0},
{x: width + 15, y: 10},
{x: width + tabWidth / 2 - 20, y: (height / 3) * 2},
{x: width + tabWidth / 2 + 20, y: (height / 3) * 2},
{x: width + tabWidth - 15, y: 10},
{x: width + tabWidth - 5, y: 0},
{x: width + tabWidth + 10, y: 0},
]);
const right = shape
.line()
.x((d) => d.x)
.y((d) => d.y)([
{x: width + tabWidth, y: 0},
{x: width * 2 + tabWidth, y: 0},
{x: width * 2 + tabWidth, y: height},
{x: 0, y: height},
{x: 0, y: 0},
]);
const d = `${left} ${tab1} ${right}`;
export default class tabBar extends Component {
value = new Animated.Value(-width);
render() {
const {value} = this;
return (
<View style={styles.container}>
<View {...{width}} style={{backgroundColor: 'transparent'}}>
<AnimatedSvg
width={width * 2 + tabWidth}
{...{height}}
style={{
transform: [{translateX: value}],
}}>
<Path key="path" {...{d}} fill="white" />
</AnimatedSvg>
<View style={StyleSheet.absoluteFill}>
<StaticTabBar {...{value}} />
</View>
<SafeAreaView style={styles.safeArea} />
</View>
</View>
);
}
}
class StaticTabBar extends Component {
constructor(props) {
super(props);
this.value = tabs.map(
(item, index) => new Animated.Value(index === 0 ? 1 : 0),
);
}
onPress = (index) => {
const {value} = this.props;
Animated.parallel([
Animated.parallel([
...this.value.map((item, i) => {
if (index !== i) {
return Animated.timing(item, {
toValue: 0,
duration: 400,
useNativeDriver: true,
});
}
}),
]),
Animated.parallel([
Animated.timing(value, {
toValue: -width + tabWidth * index,
useNativeDriver: true,
duration: 450,
}),
...this.value.map((item, i) => {
if (index === i) {
return Animated.timing(item, {
toValue: 1,
duration: 400,
useNativeDriver: true,
});
}
}),
]),
]).start();
};
render() {
const {value} = this.props;
return (
<View style={styles.container1}>
{tabs.map(({name}, index) => {
const activeValue = this.value[index];
const opacity = value.interpolate({
inputRange: [
-width + tabWidth * (index - 1),
-width + tabWidth * index,
-width + tabWidth * (index + 1),
],
outputRange: [1, 0, 1],
extrapolate: 'clamp',
});
const translateIcons = value.interpolate({
inputRange: [
-width + tabWidth * (index - 1),
-width + tabWidth * index,
-width + tabWidth * (index + 1),
],
outputRange: [0, 10, 0],
extrapolate: 'clamp',
});
const translateY = activeValue.interpolate({
inputRange: [0, 1],
outputRange: [tabWidth, 0],
});
const opacityValue = activeValue.interpolate({
inputRange: [0, 0.7, 1],
outputRange: [0, 0, 1],
});
const translateX = value.interpolate({
inputRange: [
-width + tabWidth * (index - 1),
-width + tabWidth * index,
-width + tabWidth * (index + 1),
],
outputRange: [-tabWidth, 0, tabWidth],
});
return (
<>
<Animated.View
style={[
{
left: index * tabWidth,
width: tabWidth,
transform: [{translateY}, {translateX}],
opacity: opacityValue,
},
styles.movingCircle,
]}>
<View style={styles.circle}>
<Animated.Image
source={{
uri: name,
}}
style={styles.activeIcon}
/>
</View>
</Animated.View>
<TouchableWithoutFeedback onPress={() => this.onPress(index)}>
<View style={styles.active}>
<Animated.Image
source={{
uri: name,
}}
style={{
...styles.inactive,
opacity,
transform: [{translateY: translateIcons}],
}}
/>
</View>
</TouchableWithoutFeedback>
</>
);
})}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-end',
},
safeArea: {
backgroundColor: 'white',
},
container1: {
height,
width,
flexDirection: 'row',
},
circle: {
height: 60,
width: 60,
borderRadius: 30,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
},
movingCircle: {
position: 'absolute',
alignItems: 'center',
top: -30,
},
inactive: {
height: 25,
width: 25,
tintColor: '#192f6a',
},
active: {
width: tabWidth,
height,
alignItems: 'center',
justifyContent: 'center',
},
activeIcon: {
height: 25,
width: 25,
tintColor: '#192f6a',
},
});

最新更新