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的代码如下:
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;
}
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();
}
完整代码:
/** * 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,});
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,{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'
},})
如何使用:
<Toast ref="toast"/>
//省略...
<Text style={styles.styleText} onPress={()=>{
this.refs.toast.show('你点击了忘记密码!',3000);}}>
忘记密码?
</Text>
//省略...
获取验证码
在很多应用开发中都会涉及到获取手机验证码的场景,例如登录或者注册获取验证码。如下图:
那么按照自定义组件的流程,先添加构造函数,并定义必须的一些字段(相关属性),并完成初始化:
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,构造函数:
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)
}
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},{color: ((!counting && selfEnable) ? textStyle.color : disableColor || 'gray')}]}>{timerTitle}</Text>
</View>
</TouchableOpacity>
)
}
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
所以,获取验证码的完整代码如下:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React,PropTypes} from 'react';
import {
Text,StyleSheet,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,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},{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,justifyContent: 'center',styleTextCode: { fontSize: 12,color: '#dc1466',textAlign: 'center',});
如何使用?
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)=>{
}}/>