关于如何集成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', }, });