关于如何集成TabBar,请看上一节《【搭建react-native项目框架】3.集成第三方路由和tab页》
本节只讲解如何自定义TabBar的中间按钮,以及播放时旋转动画的实现。
关于动画与播放器的集成可以参考https://github.com/pheromone/react-native-videoDemo,但这个项目目前安卓存在bug,所以我只借鉴了动画部分。
还是先来看效果图
其实思路很简单,首先要使TabBar把中间按钮的位置空出来,然后摆上一个悬浮的按钮,就能实现中间按钮了。
1.设置CustomTabBar的placeMiddle属性为true,这个属性表示是否把中间按钮的位置留出来。
<CustomTabBar
tabNames={tabNames} //tab名称
placeMiddle={true} //中间是否占位,即中间是否需要用特殊按钮样式等
/>
2.悬浮效果可以用绝对定位来实现,见下图
在components下新建一个playButton.js文件。先写一个外层View,绝对定位到页面底部中间位置;再做个带边框的圆形View;然后用长方形View将圆形View下半部的边框覆盖;最后写个旋转图和按钮。
<View style={[styles.playBox]}>
<View style={[styles.playBoxCircle]} />
<View style={[styles.playBoxBackground]} />
<TouchableOpacity onPress={() => this.play()} underlayColor="transparent" style={[styles.playInner]}>
<View style={[styles.playInnerBox]}>
<Animated.Image
source={require('./resources/images/src/miss.jpg')}
style={[styles.playBackImage, {
transform: [
//使用interpolate插值函数,实现了从数值单位的映射转换,上面角度从0到1,这里把它变成0-360的变化
{rotateZ: this.state.rotateValue.interpolate({
inputRange: [0,1],
outputRange: ['0deg', '360deg'],
})},
]
}]}
/>
<Image
source={this.state.playImage}
style={[{width: Common.autoScaleSize(32), height: Common.autoScaleSize(32)}]}
/>
</View>
</TouchableOpacity>
</View>
const styles = StyleSheet.create({
playBox: {
width: Common.autoScaleSize(128),
height: Common.autoScaleSize(136),
position: 'absolute',
bottom: 0,
left: Common.autoScaleSize(311),
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
},
playBoxCircle: {
backgroundColor: '#ffffff',
width: Common.autoScaleSize(128),
height: Common.autoScaleSize(128),
borderRadius: Common.autoScaleSize(128),
position: 'absolute',
bottom: Common.autoScaleSize(8),
borderWidth: Common.autoScaleSize(1),
borderColor: '#cdcdcd',
},
playBoxBackground: {
backgroundColor: '#ffffff',
width: Common.autoScaleSize(125),
height: Common.autoScaleSize(72),
position: 'absolute',
bottom: 0,
left: Common.autoScaleSize(1),
},
playInner: {
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
position: 'absolute',
bottom: Common.autoScaleSize(20),
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
playInnerBox: {
backgroundColor: '#cdcdcd',
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
playBackImage: {
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
position: 'absolute',
},
});
3.实现旋转动画。
先构造初始旋转角度、播放状态和旋转动画
this.state = {
playImage: require('./resources/images/play.png'),
rotateValue: new Animated.Value(0), //旋转角度的初始值
};
this.isPlaying = false;
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1, //角度从0变1
duration: 15000, //从0到1的时间
easing: Easing.inOut(Easing.linear), //线性变化,匀速旋转
});
根据播放状态切换播放按钮的图标,并开始/暂停播放
play() {
this.isPlaying = !this.isPlaying;
if (this.isPlaying === true) {
this.setState({
playImage: require('./resources/images/pause.png'),
});
this.startPlay();
} else {
this.setState({
playImage: require('./resources/images/play.png'),
});
this.stopPlay();
}
}
开始播放
startPlay() {
this.playerAnimated.start(() => {
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1, //角度从0变1
duration: 15000, //从0到1的时间
easing: Easing.inOut(Easing.linear), //线性变化,匀速旋转
});
this.rotating();
});
}
暂停播放
stopPlay() {
this.state.rotateValue.stopAnimation((oneTimeRotate) => {
//计算角度比例
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1,
duration: (1-oneTimeRotate) * 15000,
easing: Easing.inOut(Easing.linear),
});
});
}
开始旋转动画
rotating() {
if (this.isPlaying) {
this.state.rotateValue.setValue(0);
this.playerAnimated.start(() => {
this.rotating()
})
}
};
4.在App.js文件中引入playButton.js
import PlayButton from "./components/playButton";
在最外层View组件底部渲染PlayButton
<View style={[{flex: 1}]}>
//Router......
<PlayButton />
</View>
最后上完整的playButton.js代码
import React, { Component } from 'react';
import {
Animated,
Easing,
StyleSheet,
View,
TouchableOpacity,
Image,
} from "react-native";
//自定义组件
import Common from "./common";
//页面
import PlayScreen from '../views/play'; //播放页
export default class PlayButton extends Component {
constructor(props) {
super(props);
//使用Animated.Value设定初始化值(角度)
this.state = {
playImage: require('../resources/images/play.png'),
rotateValue: new Animated.Value(0), //旋转角度的初始值
};
this.isPlaying = false;
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1, //角度从0变1
duration: 15000, //从0到1的时间
easing: Easing.inOut(Easing.linear), //线性变化,匀速旋转
});
}
play() {
this.isPlaying = !this.isPlaying;
if (this.isPlaying === true) {
this.setState({
playImage: require('../resources/images/pause.png'),
});
this.startPlay();
} else {
this.setState({
playImage: require('../resources/images/play.png'),
});
this.stopPlay();
}
}
rotating() {
if (this.isPlaying) {
this.state.rotateValue.setValue(0);
this.playerAnimated.start(() => {
this.rotating()
})
}
};
startPlay() {
this.playerAnimated.start(() => {
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1, //角度从0变1
duration: 15000, //从0到1的时间
easing: Easing.inOut(Easing.linear), //线性变化,匀速旋转
});
this.rotating();
});
}
stopPlay() {
this.state.rotateValue.stopAnimation((oneTimeRotate) => {
//计算角度比例
this.playerAnimated = Animated.timing(this.state.rotateValue, {
toValue: 1,
duration: (1-oneTimeRotate) * 15000,
easing: Easing.inOut(Easing.linear),
});
});
}
render() {
return (
<View style={[styles.playBox]}>
<View style={[styles.playBoxCircle]} />
<View style={[styles.playBoxBackground]} />
<TouchableOpacity onPress={() => this.play()} underlayColor="transparent" style={[styles.playInner]}>
<View style={[styles.playInnerBox]}>
<Animated.Image
source={require('../resources/images/src/miss.jpg')}
style={[styles.playBackImage, {
transform: [
//使用interpolate插值函数,实现了从数值单位的映射转换,上面角度从0到1,这里把它变成0-360的变化
{rotateZ: this.state.rotateValue.interpolate({
inputRange: [0,1],
outputRange: ['0deg', '360deg'],
})},
]
}]}
/>
<Image
source={this.state.playImage}
style={[{width: Common.autoScaleSize(32), height: Common.autoScaleSize(32)}]}
/>
</View>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
playBox: {
width: Common.autoScaleSize(128),
height: Common.autoScaleSize(136),
position: 'absolute',
bottom: 0,
left: Common.autoScaleSize(311),
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
},
playBoxCircle: {
backgroundColor: '#ffffff',
width: Common.autoScaleSize(128),
height: Common.autoScaleSize(128),
borderRadius: Common.autoScaleSize(128),
position: 'absolute',
bottom: Common.autoScaleSize(8),
borderWidth: Common.autoScaleSize(1),
borderColor: '#cdcdcd',
},
playBoxBackground: {
backgroundColor: '#ffffff',
width: Common.autoScaleSize(128),
height: Common.autoScaleSize(101),
position: 'absolute',
bottom: 0,
},
playInner: {
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
position: 'absolute',
bottom: Common.autoScaleSize(20),
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
playInnerBox: {
backgroundColor: '#cdcdcd',
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
playBackImage: {
width: Common.autoScaleSize(101),
height: Common.autoScaleSize(101),
borderRadius: Common.autoScaleSize(101),
position: 'absolute',
},
});