React Native通过近两年的迭代和维护,最新版本已经到了0.45.1,关于最新版本的介绍请查看我之前的博客:0.45新特性。话说回来,尽管迭代的挺快,但还是有很多坑,很多基础的组件和API还是不完善。
今天给大家带来的自定义小专题,其实对于React Native来说,自定义组件的过程更像是Android、iOS的组合控件。大体步骤有如下几个步骤(不完全准确,但是方向大体准确): 1,定义构造函数constructor; 2,定义组件属性propTypes; 3,绘制界面; 4,添加更新界面逻辑等
自定义Toast
在系统组件中,RN为我们提供了ToastAndroid组件,但是对于iOS好像并没有直接提供,这时候我们就想到了自定义控件了。如下图所示:
我们之前讲过Animated组件,这个组件可以实现渐变,缩放,旋转等动画效果,在这里,我们可以用它来实现Toast的功能。比如,显示两秒后消失,为了对显示的位置进行设置,我们还可以设置显示的位置,所以绘制render的代码如下:
代码语言:javascript复制render() {
let top;
switch (this.props.position){
case 'top':
top=160;
break;
case 'center':
top=height /2;
break;
case 'bottom':
top=height - 160;
break;
}
let view = this.state.isShow ?
<View
style={[styles.container,{top:top}]}
pointerEvents="none"
>
<Animated.View
style={[styles.content,{opacity:this.state.opacityValue}]}
>
<Text style={styles.text}>{this.state.text}</Text>
</Animated.View>
</View> : null;
return view;
}
显示时长控制方法:
代码语言:javascript复制show(text, duration) {
if(duration>=DURATION.LENGTH_LONG){
this.duration=DURATION.LENGTH_LONG;
}else {
this.duration=DURATION.LENGTH_SHORT;
}
this.setState({
isShow: true,
text: text,
});
this.isShow=true;
this.state.opacityValue.setValue(OPACITY)
this.close();
}
完整代码:
代码语言:javascript复制/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component,PropTypes} from 'react';
import {
StyleSheet,
View,
Animated,
Dimensions,
Text,
} from 'react-native'
export const DURATION = {LENGTH_LONG: 2000, LENGTH_SHORT: 500};
const {height, width} = Dimensions.get('window');
const OPACITY=0.6;
const dismissKeyboard = require('dismissKeyboard')
export default class ToastUtil extends Component {
static propTypes = {
position: PropTypes.oneOf([
'top',
'center',
'bottom',
]),
}
static defaultProps = {
position:'center',
}
constructor(props) {
super(props);
this.state = {
isShow: false,
text: '',
opacityValue:new Animated.Value(OPACITY),
}
}
show(text, duration) {
if(duration>=DURATION.LENGTH_LONG){
this.duration=DURATION.LENGTH_LONG;
}else {
this.duration=DURATION.LENGTH_SHORT;
}
this.setState({
isShow: true,
text: text,
});
this.isShow=true;
this.state.opacityValue.setValue(OPACITY)
this.close();
}
close() {
if(!this.isShow)return;
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
Animated.timing(
this.state.opacityValue,
{
toValue: 0.0,
duration:1000,
}
).start(()=>{
this.setState({
isShow: false,
});
this.isShow=false;
});
}, this.duration);
}
componentWillUnmount() {
this.timer && clearTimeout(this.timer);
}
render() {
let top;
switch (this.props.position){
case 'top':
top=160;
break;
case 'center':
top=height /2;
break;
case 'bottom':
top=height - 160;
break;
}
let view = this.state.isShow ?
<View
style={[styles.container,{top:top}]}
pointerEvents="none"
>
<Animated.View
style={[styles.content,{opacity:this.state.opacityValue}]}
>
<Text style={styles.text}>{this.state.text}</Text>
</Animated.View>
</View> : null;
return view;
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 0,
right: 0,
alignItems: 'center',
},
content: {
backgroundColor: 'black',
opacity: OPACITY,
borderRadius: 5,
padding: 10,
},
text:{
color:'white'
},
})
如何使用:
代码语言:javascript复制<Toast ref="toast"/>
//省略...
<Text style={styles.styleText} onPress={()=>{
this.refs.toast.show('你点击了忘记密码!',3000);}}>
忘记密码?
</Text>
//省略...
获取验证码
在很多应用开发中都会涉及到获取手机验证码的场景,例如登录或者注册获取验证码。如下图:
那么按照自定义组件的流程,先添加构造函数,并定义必须的一些字段(相关属性),并完成初始化:
代码语言:javascript复制static propTypes = {
style: PropTypes.object,//style属性
textStyle: Text.propTypes.style,//文本文字
onClick: PropTypes.func,//点击事件
disableColor: PropTypes.string,//倒计时过程中颜色
timerTitle: PropTypes.string,//倒计时文本
enable: React.PropTypes.oneOfType([React.PropTypes.bool,React.PropTypes.number])
};
2,构造函数:
代码语言:javascript复制constructor(props) {
super(props)
this.state = {
timerCount: this.props.timerCount || 60,//默认倒计时时间
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true,
};
this.shouldStartCountting = this.shouldStartCountting.bind(this)
this.countDownAction = this.countDownAction.bind(this)
}
3,添加绘制界面代码:
代码语言:javascript复制render() {
const {onClick, style, textStyle, disableColor} = this.props;
const {counting, timerTitle, selfEnable} = this.state;
return (
<TouchableOpacity activeOpacity={counting ? 1 : 0.8} onPress={() => {
if (!counting &&selfEnable) {
this.setState({selfEnable: false});
this.shouldStartCountting(true);
};
}}>
<View
style={styles.styleCodeView}>
<Text
style={[{fontSize: 12}, textStyle, {color: ((!counting && selfEnable) ? textStyle.color : disableColor || 'gray')}]}>{timerTitle}</Text>
</View>
</TouchableOpacity>
)
}
4,添加逻辑代码:
代码语言:javascript复制shouldStartCountting(shouldStart) {
if (this.state.counting) {
return
}
if (shouldStart) {
this.countDownAction()
this.setState({counting: true, selfEnable: false})
} else {
this.setState({selfEnable: true})
}
}
//倒计时逻辑
countDownAction() {
const codeTime = this.state.timerCount;
this.interval = setInterval(() => {
const timer = this.state.timerCount - 1
if (timer === 0) {
this.interval && clearInterval(this.interval);
this.setState({
timerCount: codeTime,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true
})
} else {
this.setState({
timerCount: timer,
timerTitle: `重新获取(${timer}s)`,
})
}
}, 1000)
}
说明: shouldStartCountting:回调函数,接受一个Bool类型的参数 1,shouldStartCountting(true),开始倒计时,倒计时结束时自动恢复初始状态 2,shouldStartCountting(false), 按钮的selfEnable会立即被置为true 所以,获取验证码的完整代码如下:
代码语言:javascript复制/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component,PropTypes} from 'react';
import {
Text,
StyleSheet,
View,
TouchableOpacity,
} from 'react-native';
var Dimensions = require('Dimensions');
var screenWidth = Dimensions.get('window').width;
export default class TimerButton extends Component {
constructor(props) {
super(props)
this.state = {
timerCount: this.props.timerCount || 60,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true,
};
this.shouldStartCountting = this.shouldStartCountting.bind(this)
this.countDownAction = this.countDownAction.bind(this)
}
static propTypes = {
style: PropTypes.object,
textStyle: Text.propTypes.style,
onClick: PropTypes.func,
disableColor: PropTypes.string,
timerTitle: PropTypes.string,
enable: React.PropTypes.oneOfType([React.PropTypes.bool,React.PropTypes.number])
};
countDownAction() {
const codeTime = this.state.timerCount;
this.interval = setInterval(() => {
const timer = this.state.timerCount - 1
if (timer === 0) {
this.interval && clearInterval(this.interval);
this.setState({
timerCount: codeTime,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true
})
} else {
this.setState({
timerCount: timer,
timerTitle: `重新获取(${timer}s)`,
})
}
}, 1000)
}
shouldStartCountting(shouldStart) {
if (this.state.counting) {
return
}
if (shouldStart) {
this.countDownAction()
this.setState({counting: true, selfEnable: false})
} else {
this.setState({selfEnable: true})
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const {onClick, style, textStyle, disableColor} = this.props;
const {counting, timerTitle, selfEnable} = this.state;
return (
<TouchableOpacity activeOpacity={counting ? 1 : 0.8} onPress={() => {
if (!counting &&selfEnable) {
this.setState({selfEnable: false});
this.shouldStartCountting(true);
};
}}>
<View
style={styles.styleCodeView}>
<Text
style={[{fontSize: 12}, textStyle, {color: ((!counting && selfEnable) ? textStyle.color : disableColor || 'gray')}]}>{timerTitle}</Text>
</View>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20
},
styleCodeView: {
height: 28,
width: screenWidth*0.22,
borderColor: '#dc1466',
borderWidth: 1,
borderRadius: 5,
justifyContent: 'center',
alignItems: 'center',
},
styleTextCode: {
fontSize: 12,
color: '#dc1466',
textAlign: 'center',
},
});
如何使用?
代码语言:javascript复制import TimerButton from './TimerButton'
var Dimensions = require('Dimensions');
var screenWidth = Dimensions.get('window').width;
//省略...
<TimerButton
style={{width: screenWidth*0.2,marginRight: 10}}
timerCount={60}
textStyle={{color: '#dc1466'}}
onclick={(start)=>{
}}/>