React Native 自定义控件专题

时间:2022-04-24
本文章向大家介绍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,
            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'
    },
})

如何使用:

<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}, textStyle, {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, {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',
    },

});

如何使用?

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