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

简介: 之前我们介绍了RN相关的知识: 是时候了解React Native了 从零学React Native之01创建第一个程序 从零学React Native之02状态机 从零学React Native之03页面导航本篇主要介绍: 1. 自定义组件 2. Alert 对话框自定义对话框之前的我都是利用React Native提供的基础组件对它们进行排列组合, 其

之前我们介绍了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行
        // {'\r\n'}确 定 回车换行后跟着确定,为了克服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}>
                        {'\r\n'}确 定
                    </Text>
                    <Text style={styles.cancelButton}
                          onPress={this.props.userCanceled}
                          numberOfLines={3}>
                        {'\r\n'}取 消
                    </Text>
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    confirmCont: {  //全屏显示 半透明 可以看到之前的控件但是不能操作了
        position:'absolute',  //声明绝对定位
        top:0,
        width:totalWidth,
        height:totalHeight,
        backgroundColor:'rgba(52,52,52,0.5)'  //rgba  a0-1  其余都是十进制数
    },
    dialogStyle: {
        position:'absolute',
        left:totalWidth/10, // 定义Dilaog起点位置
        top:totalHeight*0.4,
        width: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,
        width:totalWidth*0.35,
        height:totalHeight*0.12,
        backgroundColor:'grey',
        fontSize:20,
        color:'white',
        textAlign:'center'
    },
    cancelButton: {
        position:'absolute',
        bottom:10,
        right:10,
        width: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开发需要加上该高度
        width: componentWidth,
        backgroundColor: 'gray',
        fontSize: 20
    },
    textPromptStyle: {
        top: 30,
        left: leftStartPoint,
        //  // height:30,  // IOS开发需要加上该高度 因为IOS中TextInput不会自动设置高度
        width: componentWidth,
        fontSize: 20
    },
    passwordInputStyle: {
        top: 50,
        left: leftStartPoint,
        width: componentWidth,
        backgroundColor: 'gray',
        fontSize: 20
    },
    bigTextPrompt: {
        top: 70,
        left: leftStartPoint,
        width: 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。
这里写图片描述

相关文章
|
12月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
787 2
|
9月前
|
前端开发 UED 开发者
React 模态对话框 Modal Dialog
本文介绍如何在 React 中实现模态对话框,涵盖基本概念、常见问题及解决方案。模态对话框是一种阻止用户与页面其他部分交互的弹出窗口,常用于显示重要信息或收集输入。通过状态管理控制其显示和隐藏,需注意多组件状态同步、关闭行为一致性及样式冲突等问题。文中还介绍了动画效果和键盘导航支持等进阶功能,帮助开发者提升用户体验并确保代码的可维护性。
240 80
|
7月前
|
编解码 前端开发 开发者
React 图片组件样式自定义:常见问题与解决方案
在 React 开发中,图片组件的样式自定义常因细节问题导致布局错乱、性能损耗或交互异常。本文系统梳理常见问题及解决方案,涵盖基础样式应用、响应式设计、加载状态与性能优化等,结合代码案例帮助开发者高效实现图片组件的样式控制。重点解决图片尺寸不匹配、边框阴影不一致、移动端显示模糊、加载失败处理及懒加载等问题,并总结易错点和最佳实践,助力开发者提升开发效率和用户体验。
214 22
|
10月前
|
前端开发 UED 开发者
React 对话框组件 Dialog
本文详细介绍了如何在 React 中实现一个功能完备的对话框组件(Dialog),包括基本用法、常见问题及其解决方案,并通过代码案例进行说明。从安装依赖到创建组件、添加样式,再到解决关闭按钮失效、背景点击无效、键盘导航等问题,最后还介绍了如何添加动画效果和处理异步关闭操作。希望本文能帮助你在实际开发中更高效地使用 React 对话框组件。
365 75
|
7月前
|
Web App开发 移动开发 前端开发
React 视频播放器样式自定义实战指南
本文详细介绍了如何在React项目中实现视频播放器的样式自定义,涵盖HTML5 `&lt;video&gt;`标签的基础知识、CSS样式定制技巧及常见问题解决方案。针对全屏模式样式失效、移动端触摸事件冲突和进度条样式定制等问题提供了具体代码示例。同时,探讨了视频预加载策略和内存优化方法,并推荐了几款调试工具,帮助开发者提升用户体验和应用性能。
199 6
|
7月前
|
Web App开发 移动开发 前端开发
React音频播放器样式自定义全解析:从入门到避坑指南
在React中使用HTML5原生&lt;audio&gt;标签时,开发者常面临视觉一致性缺失、样式定制局限和交互体验割裂等问题。通过隐藏原生控件并构建自定义UI层,可以实现完全可控的播放器视觉风格,避免状态不同步等典型问题。结合事件监听、进度条拖拽、浏览器兼容性处理及性能优化技巧,可构建高性能、可维护的音频组件,满足跨平台需求。建议优先使用成熟音频库(如react-player),仅在深度定制需求时采用原生方案。
228 12
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
前端开发
React给antd中TreeSelect组件左侧加自定义图标icon
本文介绍了如何在React中为Ant Design的TreeSelect组件的每个树节点添加自定义图标,并解决了因缺少key属性而导致的警告问题,展示了如何通过递归函数处理treeData数据并为每个节点添加图标。
636 2
React给antd中TreeSelect组件左侧加自定义图标icon
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
174 4
React技术栈-React路由插件之自定义组件标签
|
12月前
|
前端开发 JavaScript API
自定义React Hooks综合指南
本文介绍了React Hooks及其在组件开发中的作用,重点讲解了自定义Hook的创建和使用方法。通过实例展示了如何创建`useWindowWidth`、`useFetch`和`useForm`等自定义Hook,并分享了使用自定义Hook的最佳实践。文章强调了自定义Hook在提高代码复用性和组件可维护性方面的重要性。
246 0