React Native 自定义控件之验证码和Toast

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)
    }

3,添加绘制界面代码

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>
        )
    }

4,添加逻辑代码

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)=>{
 }}/>

相关文章

react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接...
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc ...