• 从零学React Native之04自定义对话框


    之前我们介绍了RN相关的知识:
    是时候了解React Native了
    从零学React Native之01创建第一个程序
    从零学React Native之02状态机
    从零学React Native之03页面导航

    本篇主要介绍:
    1. 自定义组件
    2. Alert 对话框

    自定义对话框

    之前的我都是利用React Native提供的基础组件对它们进行排列组合, 其实自定义也很简单, 我们还是拿上一篇文章的例子进行扩展。

    当我们点击注册的时候,可以弹出一个对话框,让用户确认一下,如下图:


    接下来就来试试,
    首先在项目目录下创建ConfirmDialog.js
    代码如下:

    import React, { Component } from 'react';
    import {
        StyleSheet,
        Text,  // RN提供的组件
        View,
        BackAndroid
    } from 'react-native';
    
    let Dimensions = require('Dimensions');
    let totalWidth = Dimensions.get('window').width;//宽
    let totalHeight = Dimensions.get('window').height;//高
    
    // 直接导出组件,不用写 module.exports=ConfirmDialog;了
    export default class ConfirmDialog extends Component {
        render() {
            // onPress事件直接与父组件传递进来的属性挂接
            //numberOfLines 可显示3行
            // {'
    '}确 定 回车换行后跟着确定,为了克服Text组件不能垂直居中显示
            return (
                <View style={styles.confirmCont}>
                    <View style={styles.dialogStyle}>
                        <Text style={styles.textPrompt}>
                            {this.props.promptToUser}
                        </Text>
                        <Text style={styles.yesButton}
                              onPress={this.props.userConfirmed}
                              numberOfLines={3}>
                            {'
    '}确 定
                        </Text>
                        <Text style={styles.cancelButton}
                              onPress={this.props.userCanceled}
                              numberOfLines={3}>
                            {'
    '}取 消
                        </Text>
                    </View>
                </View>
            );
        }
    }
    
    const styles = StyleSheet.create({
        confirmCont: {  //全屏显示 半透明 可以看到之前的控件但是不能操作了
            position:'absolute',  //声明绝对定位
            top:0,
            totalWidth,
            height:totalHeight,
            backgroundColor:'rgba(52,52,52,0.5)'  //rgba  a0-1  其余都是十进制数
        },
        dialogStyle: {
            position:'absolute',
            left:totalWidth/10, // 定义Dilaog起点位置
            top:totalHeight*0.4,
            totalWidth*0.8,
            height:totalHeight*0.3,
            backgroundColor:'white'
        },
        textPrompt: {
            position:'absolute',
            top:10,
            left:10,
            fontSize:20,
            color:'black'
        },
        yesButton: {
            position:'absolute',
            bottom:10,
            left:10,
            totalWidth*0.35,
            height:totalHeight*0.12,
            backgroundColor:'grey',
            fontSize:20,
            color:'white',
            textAlign:'center'
        },
        cancelButton: {
            position:'absolute',
            bottom:10,
            right:10,
            totalWidth*0.35,
            height:totalHeight*0.12,
            backgroundColor:'grey',
            fontSize:20,
            color:'white',
            textAlign:'center'
        }
    });

    可以看到,上面的布局使用绝对布局,通过把Text和View拼装到一起组成了对话框的组件, 最外层的View是一个全屏的遮罩.
    颜色值是rgba, 前三个数的范围 0-255 分别表示红色,绿色和蓝色, 最后一位数范围是0-1 1表示完全不透明, 0表示完全透明。颜色值也可以 用#开头后面跟着6位16进制的数表示,没两位表示一种颜色, 分别为RGB。

    我们的对话框组件用到了几个属性, 如:{this.props.promptToUser} 这些属性需要在使用该组件的时候传递进来。

    挂载组件

    接下来我们需要修改上一篇文章中用到的RegisterLeaf.js文件, 这个文件我们之前用来创建注册页面。
    因为涉及到了对话框的显示和隐藏, 我们需要添加一个新的状态机变量。该变量为了控制对话框的显示与隐藏。

    import React, { Component } from 'react';
    import {
        AppRegistry, //框架提供的API
        StyleSheet,
        Text,  // RN提供的组件
        View,
        TextInput // 记得引入组件
    } from 'react-native';
    //let是更完美的var
    let Dimensions = require('Dimensions');// 宽高
    let totalWidth = Dimensions.get('window').width;  //声明了三个变量,根据屏幕动态变化
    let leftStartPoint = totalWidth * 0.1;
    let componentWidth = totalWidth * 0.8;
    //导入对话框
    let ConfirmDialog=require('./ConfirmDialog');
    
    class RegisterLeaf extends Component {
    
        //构造函数, 组件创建的时候会执行
        constructor(props) {
            super(props); //必须有这句代码 父组件向子组件传递属性, 比如styles属性等
            // 声明状态机的初始值
            this.state = {
                inputedNum: '',
                inputedPw: '',
                needToConfirm: false //导入新的状态机
            };
            // ES6
            this.userPressConfirm=this.userPressConfirm.bind(this);
            //方法不绑定 其它组件没法调用
            this.userConfirmed=this.userConfirmed.bind(this);
            this.userCanceled=this.userCanceled.bind(this);
        }
        // 定义函数
        updateNum(newText) {
            this.setState((state)=> {
                return {
                    inputedNum: newText
                }
            });
        }
        // 定义函数
        updatePW(newText) {
            this.setState(()=> { // 用不到的参数也可以不用写
                return {
                    inputedPw: newText
                }
            });
        }
    
        userPressConfirm(){
            this.setState(()=>{
                return {
                    needToConfirm:true
                }
            });
    
        }
        // 对话框取消时如何处理
        userCanceled(){
            this.setState({needToConfirm:false});
            console.log("userPressConfirm");
    
        }
        // 对话框确定
        userConfirmed(){
            this.setState({needToConfirm:false});
            this.props.navigator.push({
                phoneNumber:this.state.inputedNum,
                userPW:this.state.inputedPw,
                name:'waiting'
            })
        }
    
    
        renderWithDialog(){
            return(
                <View style={styles.container}>
                    <TextInput style={styles.numberInputStyle}
                               keyboardType={'phone-pad'}
                               placeholder={'请输入手机号'}
                               onChangeText={(newText)=>this.updateNum(newText)}/>
    
                    <Text style={styles.textPromptStyle}>
                        您输入的手机号:{this.state.inputedNum}
                    </Text>
                    <TextInput secureTextEntry={true}
                               style={styles.passwordInputStyle}
                               placeholder='请输入密码'
                               onChangeText={(newText)=>this.updatePW(newText)}/>
                    <Text style={styles.bigTextPrompt}
                          onPress={this.userPressConfirm}>
                        注  册
                    </Text>
                    <ConfirmDialog userConfirmed={this.userConfirmed}
                    userCanceled={this.userCanceled}
                    promptToUser={'使用'+this.state.inputedNum+'号码登录?'}/>
                </View>
            )
        }
    
        // 绘制渲染的控件
        render() {
            // 根据不同的状态 做相应的绘制,当需要绘制对话框时,调用renderWithDialog
            if(this.state.needToConfirm)  return this.renderWithDialog();
            return (
                /*(newText)=>this.updateNum(newText)
                 它将收到的字符串为参数调用当前组件的updateNum函数,并且将updateNum函数的返回值返回
                 当前函数在输入框文本变化的时候会调用
                 语句可以改成
                 this.updateNum
                 但一定不要写成
                 this.updateNum(newText) 因为有右箭头函数的时候newText是形式参数
                 没有箭头函数的时,newText就没有定义
                 */
                <View style={styles.container}>
                    <TextInput style={styles.numberInputStyle}
                               keyboardType={'phone-pad'}
                               placeholder={'请输入手机号'}
                               onChangeText={(newText)=>this.updateNum(newText)}/>
    
                    <Text style={styles.textPromptStyle}>
                        您输入的手机号:{this.state.inputedNum}
                    </Text>
                    <TextInput secureTextEntry={true}
                               style={styles.passwordInputStyle}
                               placeholder='请输入密码'
                               onChangeText={(newText)=>this.updatePW(newText)}/>
                    <Text style={styles.bigTextPrompt}
                          onPress={this.userPressConfirm}>
                        注  册
                    </Text>
                </View>
            );
        }
    }
    // 样式  const变量只能在声明的时候赋值一次
    const styles = StyleSheet.create({
        //各个组件都没有定义高度,父View设置了flex1,他会沾满整个高度,子组件没有设置会包裹内容
        container: {
            flex: 1,  //表示宽高会自动扩展
            backgroundColor: 'white'
        },
        numberInputStyle: {
            top: 20,     // top left表示从父组件的顶端(左侧) 向下(向右) 多少位置显示
            left: leftStartPoint,
            // height:30,  // IOS开发需要加上该高度
             componentWidth,
            backgroundColor: 'gray',
            fontSize: 20
        },
        textPromptStyle: {
            top: 30,
            left: leftStartPoint,
            //  // height:30,  // IOS开发需要加上该高度 因为IOS中TextInput不会自动设置高度
             componentWidth,
            fontSize: 20
        },
        passwordInputStyle: {
            top: 50,
            left: leftStartPoint,
             componentWidth,
            backgroundColor: 'gray',
            fontSize: 20
        },
        bigTextPrompt: {
            top: 70,
            left: leftStartPoint,
             componentWidth,
            backgroundColor: 'gray',
            color: 'white',
            textAlign: 'center',//位置居中显示
            fontSize: 60
        }
    });
    module.exports=RegisterLeaf;

    可以看到,我们渲染自定义对话框时, 给对话框传递了三个属性其中userConfirmed={this.userConfirmed},userCanceled={this.userCanceled}两个属性对应的是父组件的函数,通过将子组件属性初始化为父组件的某个函数,打通了子组件向父组件通信的通道,这就是一个无参数的函数。但在需要的情况下,子组件可以通过有参数的函数向父组件传递数据。

    Android中返回键处理

    首先在RegisterLeaf组件中再增加一个函数:

        //告诉对话框什么时候时候需要拦截返回事件
        tellConfirmDialogItsStatus() {
            return this.state.needToConfirm;
        }

    记得在构造方法中绑定this

        //构造函数, 组件创建的时候会执行
        constructor(props) {
            super(props);
            //...       
    
         this.tellConfirmDialogItsStatus=this.tellConfirmDialogItsStatus.bind(this);
        }

    然后在挂接ConfirmDialog时再增加一个属性 amIStillAlive,如下:

    <ConfirmDialog userConfirmed={this.userConfirmed}
                             userCanceled={this.userCanceled}
                             amIStillAlive={this.tellConfirmDialogItsStatus}
                             promptToUser={'使用'+this.state.inputedNum+'号码登录?'}/>

    当然我们还需要在ConfirmDialog时再增加两个生命周期的函数,再组件挂载时监听返回键按下的事件, 组件移除时,取消监听(貌似RN还有Bug,取消监听还不能用)

      //挂载时
        componentDidMount() {
            //这个位置并不是多余的,见后面说明
            var amIStillAlive=this.props.amIStillAlive;
            BackAndroid.addEventListener('ConfirmDialogListener',()=>{
                if(amIStillAlive()){
                    this.props.userCanceled();
                    return true;
                }
                return false;
            })
        }
        componentWillUnMount() {
            //RN bug 无法取消监听
            BackAndroid.removeEventListener('ConfirmDialogListener');
        }
    

    BackAndroid API的工作机制是, 当挂接多个Listener后,用户按下返回键时,多个Listener都会监听到返回键被按下事件,执行的顺序就是添加的顺序,不会因为前面的处理函数处理了返回事件,后面的处理函数就不会执行了。这些处理函数都执行完后,只要有一个处理函数返回了true,返回键被按下事件就不会交给Android框架处理,也就是说没办法退出。

    注意componentDidMount()监听处理函数中判断条件的取值,并没有直接使用this.props.amIStillAlive(),而是先用一个变量获取属性的值,然后再将这个变量值传入。
    这个位置并没有多余,因为监听处理函数在ConfirmDialog中只是描述了它的实现,当它真正被执行时,执行的上下文并不在ConfirmDialog中。也就是说,当它被执行时,如果运行this.props.amIStillAlive() 语句,它会发现找不到this.props.amIStillAlive这个变量。而amIStillAlive却是可以找到的,并且可以通过这个函数调用RegisterLeaf中相应的函数得到正确的条件值。

    属性声明

    因为自定义组件时可以复用了, 我们开发过程中可能一个项目组有多个人同时开发,其他同事可能会用到我们自定义的组件, 但是他们使用的时候很容易忘记使用某些属性,这时候我们应该在自定义组件中声明一些属性。

    export default class ConfirmDialog extends Component {
      //....
    }
    ConfirmDialog.propTypes = {
        userConfirmed: React.PropTypes.func.isRequired,
        userCanceled: React.PropTypes.func.isRequired,
        amIStillAlive: React.PropTypes.func.isRequired,
        promptToUser:React.PropTypes.string.isRequired
    };

    上面声明的属性都是 isRequired, 如果不传递这些属性程序会在开发阶段出现警告。

    这里写图片描述
    当然除了上面两种类型, 还可以约束其它几种类型,属性确认的语法也有好几种,大家可以参考我的另一篇文章 React Native声明属性和属性确认

    我们还可以给属性指定一个默认值,当没有传递该属性时使用默认值,如:

    ConfirmDialog.defaultProps = {
        promptToUser: '确定吗?'
    };

    同时记得要将指定 promptToUser为必须的’isRequired’ 去掉.

    Alert Api对话框

    弹出对话框是程序开发中经常需要使用的UI手段,React Native也为开发者提供了Alert API, 没有特殊要求可以使用Alert Api弹出对话框, 这种对话框和原生代码的对话筐样式一样,Android和IOS样式有些区别。
    首先在RegisterLeaf.js中导入Alert组件

    import {
        ...
        Alert
    } from 'react-native';

    然后修改userPressConfirm()代码:

        //使用Alert Api
        userPressConfirm() {
            Alert.alert(
                '标题',
                '正文',
                [
                    {text:'确定',onPress:this.userConfirmed},
                    {text:'取消',onPress:this.userCanceled,style:'cancel'},
                    {text:'额外选项一',onPress:this.userCanceled},
                    {text:'额外选项二',onPress:this.userCanceled}
                ]
            );
        }

    Android选项最多支持3个, 多余的直接忽略不会报错, IOS可以支持多个。style:’cancel’ 的选项再最后显示, 该样式对Android无效, Android第一个选项空间比较大。

    这里写图片描述

    IOS弹出对话框, 点击对话框周围是无法取消对话框的, 但是Android可以取消,如果Android想做成和ios一样的效果,需要在RN 0.33版本以上。在对话框上添加如下代码:

        userPressConfirm() {
            Alert.alert(
                '标题',
                '正文',
                [
                    {text:'确定',onPress:this.userConfirmed},
                    {text:'取消',onPress:this.userCanceled,style:'cancel'},
                    {text:'额外选项一',onPress:this.userCanceled},
                    {text:'额外选项二',onPress:this.userCanceled}
                ],
                {
                    cancelable: false
                }
            );
        }

    更多精彩请关注微信公众账号likeDev,公众账号名称:爱上Android。
    这里写图片描述

  • 相关阅读:
    将新的rpm包添加到本地yum源
    linux cp命令直接覆盖不提示按Y/N的方法
    十一月二十一学习报告
    十一月二十学习报告
    十一月十九学习报告
    十一月十八学习报告
    十一月十七学习报告
    十一月十五学习报告
    十一月十四学习报告
    十一月十三学习报告
  • 原文地址:https://www.cnblogs.com/hehe520/p/6329898.html
Copyright © 2020-2023  润新知