如何在React Native中创建一个自定义警报对话框

1,161 阅读16分钟

提示框是网络的一个固有的组成部分,它们的效用随其使用情况而变化。它们被用来显示消息、警告、警报和确认同意。

通过提醒对话框,用户通常可以得到同意、不同意和取消的按钮选项。有时警报也被用来记录用户的输入,但这取决于平台。

在这篇文章中,我们将学习如何在React Native中创建一个自定义的警报对话框,以满足你的项目的需要。

简介

React Native提供了一个Alert API,可以用来在Android和iOS上显示本地警报对话框。但本地警报对话框有一些限制。

例如,在Android上我们不能显示三个以上的按钮,也没有提供捕捉用户输入的选项。尽管iOS允许我们有很多按钮,并让用户输入数据,但我们仍然不能显示图片、图表,或者除了文字以外的任何形式的自定义。

为了处理这些限制,我们需要创建自定义警报对话框。一个自定义的警报对话框可以像模态一样行动,并且可以支持组件。

警报框的属性

在定制任何本地组件之前,我们应该对其架构和事件处理有清晰的了解。

例如,一个按钮拥有多个属性,如标签和膨胀度。一个按钮还拥有诸如按下、按住、释放、悬停等事件。当我们定制它时,我们需要考虑所有这些属性。否则,我们将失去外观、感觉和功能。

一个本地警报框有以下属性。

  1. 标题 - 一个文本标题,以表明该警报的目的。Android和iOS支持。
  2. 信息 - 解释通知或警告的文本信息。安卓和iOS支持
  3. 按钮--安卓支持最多三个按钮,而iOS支持无限的按钮。
  4. 外部敲击 - 安卓系统的警报可以通过敲击警报外部来关闭。
  5. onPress - 按一下按钮就可以打电话的功能。安卓和iOS都支持这个功能
  6. onDismiss - 当警报关闭时呼叫的功能。只有安卓支持这个功能
  7. Prompt - 允许用户在输入区域输入数据。只有iOS支持这个功能
  8. 后退按钮 - 默认情况下,在Android中,警报会在按下后退按钮时关闭。

在定制警报框时,我们需要考虑所有这些属性。

警报对话框的用户界面和架构

让我们看看本地Alert的外观和不同元素在上面的位置。Android和iOS的Alert在外观和感觉上都是不同的。

Alert Dialog UI Android Alert Dialog UI iOS

安卓规格

根据Material design的概念,Android警报对话框的排版和颜色如下。

元素类别属性价值
容器最大宽度
边框半径
背景颜色
280
2
#fafafa
标题文本在表面上颜色
不透明度
字体重量
大小
大小写
#000000
100%
粗体
22
句子大小写
支持性文本在表面上颜色
不透明度
字体重量
大小
案例
#000000
100%
Regular
15
Sentence Case
按钮文本初级颜色
不透明度
字体重量
大小
大小写
#387ef5
100%
500
16
大写字母
刮擦在表面上颜色
不透明度
#232F34
32%

其他属性,如高度、宽度、填充、边距等,如下。

用于小的按钮动作。

Smaller Button Actions in Custom Alert Dialog

来源:material.io

对于长的按钮动作。

Long Button Actions in Custom Alert Dialog

来源:material.io

其他属性包括
仰角 - 24dp
位置 - 中心
从两侧的最小边距 - 48dp

iOS的规格

同样地,对于iOS,我们有以下规格。

元素类别属性价值
容器表面边框半径
最大宽度
背景颜色
z-index
13px
270px
#f8f8f8
10
标题文本在表面上颜色
Padding
Margin Top
Align
Font
Size
Case
#000000
12px 16px 7px
8px
Center
600
17px
Sentence Case
支持性文本在表面上颜色
Align
Padding
Font
Size
Case
#000000
中心
0px 16px 21px
常规
13
句子大小写
按钮容器普通属性
OK按钮
单行多按钮
- 普通
- 非OK按钮
右边距
最小高度
边框顶部
字体大小
颜色
行高
字体重量
最小宽度
右边框
-0.55px
44px
0.55px solid #dbdbdf
17px
#387ef5
20px
700
50%
0.55px solid #dbdbdf
剪切在表面上颜色
不透明度
#000000
30%

这些信息收集自Ionic AlertController 文档。

自定义警报库和包

GitHub上有一些库可以让你创建自定义警报框。其中一些是 react-native-awesome-alerts, react-native-dialog, 和 react-native-modal。你可以试试这些库,但在这篇文章中,我们将在没有它们的情况下定制警报框。

警报和模态的区别

Alert和Modal在语义上是相同的东西,但在复杂性和可用性上有区别。

提醒对话框是为了以最简单的方式显示简短的信息,这就是为什么它们的功能有限。

另一方面,模态是用于复杂的显示。它们要求我们自己定义整个内容。默认情况下,它们提供事件监听器,如后退按钮处理程序。

定制警报对话框

让我们先看看我们要定制的东西。我们应该记住,警报是用来显示重要信息的。这可能是错误、警告或通知。它们不是用来显示图片或填写表格的。对于这一点,你应该使用模版。

在Alert中,我们将进行自定义。

  1. 对话框的背景颜色
  2. 标题和信息的字体颜色、大小、重量等。
  3. 按钮的字体颜色、背景颜色和边框样式

由于React Native调用了Android和iOS的本地Alert组件,它没有提供一个直接的方法来定制它们。警报是有明确用途的固定组件,因此在Android和iOS中不可定制。安卓开发者使用Dialog 类来实现这一点。

在我们的案例中,我们将使用React Native的Modal API。使用这个API的好处是。

  1. 我们不需要担心我们的自定义警报的位置。它将停留在整个应用程序的顶部
  2. Android的后退按钮和Apple TV的菜单按钮将被自动处理。

React Native Modal的简要介绍

React Native Modal API提供了一个容器,它被显示在其包围的View 。一个布尔道具visible 被传递给Modal 组件来显示或隐藏它。还有其他的道具,但我们不关心它们,因为它们对Alert没有用。

这就是Modal 的工作方式。

import React, { useState } from 'react';
import { Modal, Text, Pressable, View } from 'react-native';

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  return (
    <View>
      <Modal
        animationType="fade"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => {
          setModalVisible(!modalVisible);
        }}
      >
        <View>
          /**
          * Anything here will display on Modal.
          * We need to create background overlay and Alert box.
          */
        </View>
      </Modal>

      <View>
        /**
        * Main App content. This will get hidden when modal opens up
        */
      </View>
    </View>
  );
};

export default App;

modalVisible 是用于显示或隐藏Modal 的状态变量。你可以将模态保留在一个页面上,或者将整个应用包围在它的父View 。最好是使用像Redux这样的存储和状态管理库,因为它可以帮助你在应用程序的任何地方改变modalVisible 变量。

创建一个按钮来打开Alert

首先,我们将需要一个事件来改变modalVisible 的值。在我们的代码中,我们将使用一个按钮。在按下这个按钮时,modalVisible 将变成真,并显示一个Modal 。为了创建这个按钮,我们将使用Pressable 组件。

import React, { useState } from 'react';
import { Modal, Text, Pressable, View } from 'react-native';

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  return (
    <View>
      <Modal
        animationType="fade"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => {
          setModalVisible(!modalVisible);
        }}
      >
        <View>
          /**
          * Anything here will display on Modal.
          * We need to create background overlay and Alert box.
          */
        </View>
      </Modal>

      <View>
        <Pressable
          style={[styles.button, styles.buttonOpen]}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.textStyle}>Show Modal</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

});

export default App;

在这段代码中,我们添加了一些造型,以改善按钮和应用程序的其他部分的外观和感觉。目前,它将显示一个按钮。点击这个按钮将打开模态。渲染后的输出将看起来像这样。

Show Modal Output on Android and iOS

创建警报对话框的用户界面

下一步是创建模态UI。但首先,我们需要定义一种方法来区分两个操作系统的默认UI。iOS有一个与Android不同的视图。为了识别操作系统,React Native提供了平台API。

让我们从创建背景开始。正如我们在UI和架构部分所讨论的,Android上的背景色和不透明度分别为#232F34和0.32。对于iOS,这些值分别是#000000和0.3。

import React, { useState } from 'react';
import { Modal, Text, Pressable, View, Platform } from 'react-native';

const App = () => {
  const [modalVisible, setModalVisible] = useState(false);
  return (
    <View>
      <Modal
        animationType="fade"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => {
          setModalVisible(!modalVisible);
        }}
      >
        <Pressable style={[Platform.OS === "ios" ? styles.iOSBackdrop : styles.androidBackdrop, styles.backdrop]} onPress={() => setModalVisible(false)} />
        <View>
          /**
          * Anything here will display on Modal.
          * We need to create background overlay and Alert box.
          */
        </View>
      </Modal>

      <View>
        <Pressable
          style={[styles.button, styles.buttonOpen]}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.textStyle}>Show Modal</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

  iOSBackdrop: {
    backgroundColor: "#000000",
    opacity: 0.3
  },
  androidBackdrop: {
    backgroundColor: "#232f34",
    opacity: 0.32
  },
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  }
});

export default App;

渲染后的输出结果将看起来像这样。

Alert Dialog UI for Android and iOS

请注意,我们使用Pressable 组件来创建背景。这是因为我们想添加在按下背景墙时关闭模态的功能。

接下来我们将在这个背景板上创建一个警报对话框。但首先,我们应该把Modal 放到一个单独的组件中。这将有助于以不同的风格调用我们的自定义Alert

import React, { useState } from "react";
import { Alert, Modal, StyleSheet, Text, Pressable, View, Platform } from "react-native";
const CustomAlert = (props) => {
  return (
    <Modal
        animationType="fade"
        transparent={true}
        visible={props.modalVisible}
        onRequestClose={() => {
          props.setModalVisible(false);
        }}
      >
        <Pressable style={[Platform.OS === "ios" ? styles.iOSBackdrop : styles.androidBackdrop, styles.backdrop]} onPress={() => props.setModalVisible(false)} />
        <View>

        </View>
      </Modal>
  )
}
const App = () => {
  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View style={styles.centeredView}>
      <CustomAlert modalVisible={modalVisible} setModalVisible={setModalVisible} />
      <View>
        <Pressable
          style={[styles.button, styles.buttonOpen]}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.textStyle}>Show Modal</Text>
        </Pressable>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

  iOSBackdrop: {
    backgroundColor: "#000000",
    opacity: 0.3
  },
  androidBackdrop: {
    backgroundColor: "#232f34",
    opacity: 0.32
  },
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  }
});
export default App;

我们已经创建了一个不同的组件,名称为CustomAlert ,并将我们的Modal 放在其中。

modalVisible 的状态在App.js ,但请记住,更好的方法是使用一个中央存储管理库,如Redux。Redux将使整个应用程序中的所有组件都能访问modalVisible 状态,而且不需要将modalVisible 作为道具传递。

现在是设置Modal 的默认值的时候了。这些值将设计出与Android和iOS的本地警报框类似的盒子。

我们已经在UI和架构部分定义了所有的值。让我们从安卓开始。

安卓定制的警报对话框

默认值是。

样式属性
箱子
背景颜色
最大宽度
边距
高度
边框半径
透明
280
48
24
2
标题
边距
颜色
字体大小
字体重量
24
#000000
22
粗体字
信息
左边的保证金
右边的保证金
底边的保证金
颜色
字体大小
字体重量
24
24
24
#000000
15
正常
按钮组
边距
0 0 8 24
按钮
边距顶部
边距右侧
填充物
颜色
字体大小
字体重量
文本转换
背景颜色
12
8
10
#387ef5
16
500
大写
透明

这些默认值是在CustomAlert 组件中定义的。在用户没有提供值的情况下,这些值会被使用。

CustomAlert 组件的不同道具是。

支持价值使用
modalVisibletrue|false需要要显示/隐藏模态
setModalVisible功能必需的改变modalVisible的值
标题符号可选的设置警报框的标题
消息符号可选的设置消息
安卓
{
    container: {
        backgroundColor: String
    },
    title: {
        color: String,
        fontFamily: String,
        fontSize: Number,
        fontWeight: String,
    },
    message: {
        color: String,
        fontFamily: String,
        fontSize: Number,
        fontWeight: String
    },
}
所有字段可选设置安卓应用的提示框、标题和消息的样式
ios
{
    container: {
        backgroundColor: String
    },
    title: {
        color: String,
        fontFamily: String,
        fontSize: Number,
        fontWeight: String,
    },
    message: {
        color: String,
        fontFamily: String,
        fontSize: Number,
        fontWeight: String
    },
}
所有字段可选设置iOS应用的提示框、标题和信息的样式。
按钮
[
  {
      text: String,
      func: Function,
      styles: {
        color: String,
        fontSize: Number,
        fontWeight: String,
        fontFamily: String,
        textTransform: String,
        backgroundColor: String
      }
  }
]
所有字段可选设置按钮的属性。这里有几个要点。

  1. Android最多支持三个按钮
  2. 如果没有提供按钮文本,那么它将使用OK、CANCEL、ASK ME LATER
  3. 所有的按钮都将关闭警报框并运行所提供的功能。
  4. 如果没有提供任何一个按钮,那么它将默认显示 "确定 "按钮。

请看这段代码,了解CustomAlert 是如何定义默认值的。

const CustomAlert = (props) => {

  const [androidDefaults, setAndroidDefaults] = useState({
    container: {
      backgroundColor: (props.android && props.android.container && props.android.container.backgroundColor) || '#FAFAFA',
    },
    title: {
      color: (props.android && props.android.title && props.android.title.color) || '#000000',
      fontFamily: (props.android && props.android.title && props.android.title.fontFamily) || 'initial',
      fontSize: (props.android && props.android.title && props.android.title.fontSize) || 22,
      fontWeight: (props.android && props.android.title && props.android.title.fontWeight) || 'bold',
    },
    message: {
      color: (props.android && props.android.message && props.android.message.color) || '#000000',
      fontFamily: (props.android && props.android.message && props.android.message.fontFamily) || 'initial',
      fontSize: (props.android && props.android.message && props.android.message.fontSize) || 15,
      fontWeight: (props.android && props.android.message && props.android.message.fontWeight) || 'normal',
    },
    button: {
      color: '#387ef5',
      fontFamily: 'initial',
      fontSize: 16,
      fontWeight: '500',
      textTransform: 'uppercase',
      backgroundColor: 'transparent',
    },
  });

  return (
    <Modal
        animationType="fade"
        transparent={true}
        visible={props.modalVisible}
        onRequestClose={() => {
          props.setModalVisible(false);
        }}
      >
        <Pressable style={[Platform.OS === "ios" ? styles.iOSBackdrop : styles.androidBackdrop, styles.backdrop]} onPress={() => props.setModalVisible(false)} />
        <View style={styles.alertBox}>
        {
          Platform.OS === "ios" ? 
          null
          :
          <View style={[styles.androidAlertBox, androidDefaults.container]}>
            <Text style={[styles.androidTitle, androidDefaults.title]}>{props.title || 'Message'}</Text>
            <Text style={[styles.androidMessage, androidDefaults.message]}>{props.message || ''}</Text>
          </View>
        }
        </View>


      </Modal>
  )
}

有些造型是为了布局,这就是为什么我们不提供选项来改变它们。这些是在StyleSheet 对象中声明的。

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

  iOSBackdrop: {
    backgroundColor: "#000000",
    opacity: 0.3
  },
  androidBackdrop: {
    backgroundColor: "#232f34",
    opacity: 0.4
  },
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0
  },
  alertBox: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  androidAlertBox: {
    maxWidth: 280,
    width: '100%',
    margin: 48,
    elevation: 24,
    borderRadius: 2,
  },
  androidTitle: {
    margin: 24,
  },
  androidMessage: {
    marginLeft: 24,
    marginRight: 24,
    marginBottom: 24,
  },
  androidButtonGroup: {
    marginTop: 0,
    marginRight: 0,
    marginBottom: 8,
    marginLeft: 24,
  },
  androidButton: {
    marginTop: 12,
    marginRight: 8,    
  },
  androidButtonInner: {
    padding: 10,

  }
});

如果我们使用不同的设置来运行这个,我们会得到以下的输出。

Various CustomAlert Options in RN

是时候把按钮添加到警报对话框了。安卓系统有几个规格。

  1. 单个按钮总是OK
  2. 两个按钮是CANCEL和OK
  3. 三个按钮是ASK ME LATER、CANCEL和OK。
  4. 最多可支持三个按钮
  5. 两个按钮漂浮在盒子的右侧,而第三个按钮漂浮在左侧
  6. 长的按钮显示在不同的行里

为了满足所有这些条件,我们决定为一个按钮组单独声明一个组件。让我们把它叫做AndroidButtonBox 。看看这段代码吧。

const AndroidButtonBox = () => {
    const [buttonLayoutHorizontal, setButtonLayoutHorizontal] = useState(1);
    const buttonProps = props.buttons && props.buttons.length > 0 ? props.buttons : [{}]

    return (
      <View style={[styles.androidButtonGroup, {
        flexDirection: buttonLayoutHorizontal === 1 ? "row" : "column",
      }]} onLayout={(e) => {
        if(e.nativeEvent.layout.height > 60)
          setButtonLayoutHorizontal(0);
      }}>
        {
          buttonProps.map((item, index) => {
              if(index > 2) return null;
              const alignSelfProperty = buttonProps.length > 2 && index === 0 && buttonLayoutHorizontal === 1 ?  'flex-start' : 'flex-end';
              let defaultButtonText = 'OK'
              if(buttonProps.length > 2){
                if(index === 0)
                  defaultButtonText = 'ASK ME LATER'
                else if(index === 1)
                  defaultButtonText = 'CANCEL';
              } else if (buttonProps.length === 2 && index === 0)
                defaultButtonText = 'CANCEL';
              return (
                <View style={[styles.androidButton, index === 0 && buttonLayoutHorizontal === 1 ? {flex: 1} : {}]}>
                  <Pressable onPress={() => {
                    props.setModalVisible(false)
                    if(item.func && typeof(item.func) === 'function')
                      item.func();
                  }} style={[{
                    alignSelf: alignSelfProperty, 

                  }]}>
                    <View style={[styles.androidButtonInner, {backgroundColor: (item.styles && item.styles.backgroundColor) || androidDefaults.button.backgroundColor}]}>
                      <Text
                        style={{
                          color: (item.styles && item.styles.color) || androidDefaults.button.color,
                          fontFamily: (item.styles && item.styles.fontFamily) || androidDefaults.button.fontFamily,
                          fontSize: (item.styles && item.styles.fontSize) || androidDefaults.button.fontSize,
                          fontWeight: (item.styles && item.styles.fontWeight) || androidDefaults.button.fontWeight,
                          textTransform: (item.styles && item.styles.textTransform) || androidDefaults.button.textTransform,
                        }}
                      >{item.text || defaultButtonText}</Text>
                    </View>
                  </Pressable>
                </View>
              )
            })

        }
      </View>
    );
  }

在这段代码中,我们声明了一个状态变量,buttonLayoutHorizontal 。这将被用来把按钮组的布局从列改为行。如果所有的按钮都是短的,那么它们将被显示在一个行中。

onLayout 事件被用来确定是否需要改变这个状态变量。然后,我们在通过props提供的按钮数组上运行一个循环,并创建具有适当风格的按钮。

整个代码(对于Android)看起来是这样的。

import React, { useState } from "react";
import { Alert, Modal, StyleSheet, Text, Pressable, View, Platform } from "react-native";
const CustomAlert = (props) => {

  const [androidDefaults, setAndroidDefaults] = useState({
    container: {
      backgroundColor: (props.android && props.android.container && props.android.container.backgroundColor) || '#FAFAFA',
    },
    title: {
      color: (props.android && props.android.title && props.android.title.color) || '#000000',
      fontFamily: (props.android && props.android.title && props.android.title.fontFamily) || 'initial',
      fontSize: (props.android && props.android.title && props.android.title.fontSize) || 22,
      fontWeight: (props.android && props.android.title && props.android.title.fontWeight) || 'bold',
    },
    message: {
      color: (props.android && props.android.message && props.android.message.color) || '#000000',
      fontFamily: (props.android && props.android.message && props.android.message.fontFamily) || 'initial',
      fontSize: (props.android && props.android.message && props.android.message.fontSize) || 15,
      fontWeight: (props.android && props.android.message && props.android.message.fontWeight) || 'normal',
    },
    button: {
      color: '#387ef5',
      fontFamily: 'initial',
      fontSize: 16,
      fontWeight: '500',
      textTransform: 'uppercase',
      backgroundColor: 'transparent',
    },
  });
  const AndroidButtonBox = () => {
    const [buttonLayoutHorizontal, setButtonLayoutHorizontal] = useState(1);
    const buttonProps = props.buttons && props.buttons.length > 0 ? props.buttons : [{}]

    return (
      <View style={[styles.androidButtonGroup, {
        flexDirection: buttonLayoutHorizontal === 1 ? "row" : "column",
      }]} onLayout={(e) => {
        if(e.nativeEvent.layout.height > 60)
          setButtonLayoutHorizontal(0);
      }}>
        {
          buttonProps.map((item, index) => {
              if(index > 2) return null;
              const alignSelfProperty = buttonProps.length > 2 && index === 0 && buttonLayoutHorizontal === 1 ?  'flex-start' : 'flex-end';
              let defaultButtonText = 'OK'
              if(buttonProps.length > 2){
                if(index === 0)
                  defaultButtonText = 'ASK ME LATER'
                else if(index === 1)
                  defaultButtonText = 'CANCEL';
              } else if (buttonProps.length === 2 && index === 0)
                defaultButtonText = 'CANCEL';
              return (
                <View style={[styles.androidButton, index === 0 && buttonLayoutHorizontal === 1 ? {flex: 1} : {}]}>
                  <Pressable onPress={() => {
                    props.setModalVisible(false)
                    if(item.func && typeof(item.func) === 'function')
                      item.func();
                  }} style={[{
                    alignSelf: alignSelfProperty, 

                  }]}>
                    <View style={[styles.androidButtonInner, {backgroundColor: (item.styles && item.styles.backgroundColor) || androidDefaults.button.backgroundColor}]}>
                      <Text
                        style={{
                          color: (item.styles && item.styles.color) || androidDefaults.button.color,
                          fontFamily: (item.styles && item.styles.fontFamily) || androidDefaults.button.fontFamily,
                          fontSize: (item.styles && item.styles.fontSize) || androidDefaults.button.fontSize,
                          fontWeight: (item.styles && item.styles.fontWeight) || androidDefaults.button.fontWeight,
                          textTransform: (item.styles && item.styles.textTransform) || androidDefaults.button.textTransform,
                        }}
                      >{item.text || defaultButtonText}</Text>
                    </View>
                  </Pressable>
                </View>
              )
            })

        }
      </View>
    );
  }
  return (
    <Modal
        animationType="fade"
        transparent={true}
        visible={props.modalVisible}
        onRequestClose={() => {
          props.setModalVisible(false);
        }}
      >
        <Pressable style={[Platform.OS === "ios" ? styles.iOSBackdrop : styles.androidBackdrop, styles.backdrop]} onPress={() => props.setModalVisible(false)} />
        <View style={styles.alertBox}>
        {
          Platform.OS === "ios" ? 
          null
          :
          <View style={[styles.androidAlertBox, androidDefaults.container]}>
            <Text style={[styles.androidTitle, androidDefaults.title]}>{props.title || 'Message'}</Text>
            <Text style={[styles.androidMessage, androidDefaults.message]}>{props.message || ''}</Text>
            <AndroidButtonBox />
          </View>
        }
        </View>


      </Modal>
  )
}
const App = () => {
  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View style={styles.centeredView}>
      <CustomAlert 
          modalVisible={modalVisible} 
          setModalVisible={setModalVisible}
          title={'Alert Title'}
          message={'This is some message'} 
          android={{
            container: {
              backgroundColor: 'yellow'
            },
            title: {
              color: 'red',
              fontFamily: 'Roboto',
              fontSize: 26,
              fontWeight: 'regular',
            },
            message: {
              color: 'blue',
              fontFamily: 'Roboto',
              fontSize: 16,
              fontWeight: 'regular',
            },
          }}
          buttons={[{
            text: 'no'
          },{
            text: 'Yes',
            func: () => {console.log('Yes Pressed')},
            styles: {
              color: '#FFFFFF',
              fontSize: 18,
              fontWeight: 'bold',
              fontFamily: 'Roboto',
              textTransform: 'none',
              backgroundColor: '#000000'
            }
          }]}
      />
      <View>
        <Pressable
          style={[styles.button, styles.buttonOpen]}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.textStyle}>Show Modal</Text>
        </Pressable>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

  iOSBackdrop: {
    backgroundColor: "#000000",
    opacity: 0.3
  },
  androidBackdrop: {
    backgroundColor: "#232f34",
    opacity: 0.4
  },
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0
  },
  alertBox: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  androidAlertBox: {
    maxWidth: 280,
    width: '100%',
    margin: 48,
    elevation: 24,
    borderRadius: 2,
  },
  androidTitle: {
    margin: 24,
  },
  androidMessage: {
    marginLeft: 24,
    marginRight: 24,
    marginBottom: 24,
  },
  androidButtonGroup: {
    marginTop: 0,
    marginRight: 0,
    marginBottom: 8,
    marginLeft: 24,
  },
  androidButton: {
    marginTop: 12,
    marginRight: 8,    
  },
  androidButtonInner: {
    padding: 10,

  }
});
export default App;

CustomAlert 的不同值的渲染输出是。

Different Value Outputs for RN CustomAlert

iOS自定义警报对话框

iOS警报框的代码将与Android类似,只是在样式上有所改变。

在iOS中,所有的实体如标题、信息和按钮都是居中对齐的。可以有任何数量的按钮。一个iOS和Android的CustomAlert 框的完整代码是。

import React, { useState } from "react";
import { Alert, Modal, StyleSheet, Text, Pressable, View, Platform } from "react-native";
const CustomAlert = (props) => {

  const [androidDefaults, setAndroidDefaults] = useState({
    container: {
      backgroundColor: (props.android && props.android.container && props.android.container.backgroundColor) || '#FAFAFA',
    },
    title: {
      color: (props.android && props.android.title && props.android.title.color) || '#000000',
      fontFamily: (props.android && props.android.title && props.android.title.fontFamily) || 'initial',
      fontSize: (props.android && props.android.title && props.android.title.fontSize) || 22,
      fontWeight: (props.android && props.android.title && props.android.title.fontWeight) || 'bold',
    },
    message: {
      color: (props.android && props.android.message && props.android.message.color) || '#000000',
      fontFamily: (props.android && props.android.message && props.android.message.fontFamily) || 'initial',
      fontSize: (props.android && props.android.message && props.android.message.fontSize) || 15,
      fontWeight: (props.android && props.android.message && props.android.message.fontWeight) || 'normal',
    },
    button: {
      color: '#387ef5',
      fontFamily: 'initial',
      fontSize: 16,
      fontWeight: '500',
      textTransform: 'uppercase',
      backgroundColor: 'transparent',
    },
  });
  const [iOSDefaults, setIOSDefaults] = useState({
    container: {
      backgroundColor: (props.ios && props.ios.container && props.ios.container.backgroundColor) || '#F8F8F8',
    },
    title: {
      color: (props.ios && props.ios.title && props.ios.title.color) || '#000000',
      fontFamily: (props.ios && props.ios.title && props.ios.title.fontFamily) || 'initial',
      fontSize: (props.ios && props.ios.title && props.ios.title.fontSize) || 17,
      fontWeight: (props.ios && props.ios.title && props.ios.title.fontWeight) || '600',
    },
    message: {
      color: (props.ios && props.ios.message && props.ios.message.color) || '#000000',
      fontFamily: (props.ios && props.ios.message && props.ios.message.fontFamily) || 'initial',
      fontSize: (props.ios && props.ios.message && props.ios.message.fontSize) || 13,
      fontWeight: (props.ios && props.ios.message && props.ios.message.fontWeight) || 'normal',
    },
    button: {
      color: '#387ef5',
      fontFamily: 'initial',
      fontSize: 17,
      fontWeight: '500',
      textTransform: 'none',
      backgroundColor: 'transparent',
    },
  });
  const AndroidButtonBox = () => {
    const [buttonLayoutHorizontal, setButtonLayoutHorizontal] = useState(1);
    const buttonProps = props.buttons && props.buttons.length > 0 ? props.buttons : [{}]

    return (
      <View style={[styles.androidButtonGroup, {
        flexDirection: buttonLayoutHorizontal === 1 ? "row" : "column",
      }]} onLayout={(e) => {
        if(e.nativeEvent.layout.height > 60)
          setButtonLayoutHorizontal(0);
      }}>
        {
          buttonProps.map((item, index) => {
              if(index > 2) return null;
              const alignSelfProperty = buttonProps.length > 2 && index === 0 && buttonLayoutHorizontal === 1 ?  'flex-start' : 'flex-end';
              let defaultButtonText = 'OK'
              if(buttonProps.length > 2){
                if(index === 0)
                  defaultButtonText = 'ASK ME LATER'
                else if(index === 1)
                  defaultButtonText = 'CANCEL';
              } else if (buttonProps.length === 2 && index === 0)
                defaultButtonText = 'CANCEL';
              return (
                <View style={[styles.androidButton, index === 0 && buttonLayoutHorizontal === 1 ? {flex: 1} : {}]}>
                  <Pressable onPress={() => {
                    props.setModalVisible(false)
                    if(item.func && typeof(item.func) === 'function')
                      item.func();
                  }} style={[{
                    alignSelf: alignSelfProperty, 

                  }]}>
                    <View style={[styles.androidButtonInner, {backgroundColor: (item.styles && item.styles.backgroundColor) || androidDefaults.button.backgroundColor}]}>
                      <Text
                        style={{
                          color: (item.styles && item.styles.color) || androidDefaults.button.color,
                          fontFamily: (item.styles && item.styles.fontFamily) || androidDefaults.button.fontFamily,
                          fontSize: (item.styles && item.styles.fontSize) || androidDefaults.button.fontSize,
                          fontWeight: (item.styles && item.styles.fontWeight) || androidDefaults.button.fontWeight,
                          textTransform: (item.styles && item.styles.textTransform) || androidDefaults.button.textTransform,
                        }}
                      >{item.text || defaultButtonText}</Text>
                    </View>
                  </Pressable>
                </View>
              )
            })

        }
      </View>
    );
  }
  const IOSButtonBox = () => {
    const buttonProps = props.buttons && props.buttons.length > 0 ? props.buttons : [{}]
    const [buttonLayoutHorizontal, setButtonLayoutHorizontal] = useState(buttonProps.length === 2 ? 1 : 0);


    return (
      <View style={[styles.iOSButtonGroup, {
        flexDirection: buttonLayoutHorizontal === 1 ? "row" : "column",
      }]} onLayout={(e) => {
        if(e.nativeEvent.layout.height > 60)
          setButtonLayoutHorizontal(0);
      }}>
        {
          buttonProps.map((item, index) => {
              let defaultButtonText = 'OK'
              if(buttonProps.length > 2){
                if(index === 0)
                  defaultButtonText = 'ASK ME LATER'
                else if(index === 1)
                  defaultButtonText = 'CANCEL';
              } else if (buttonProps.length === 2 && index === 0)
                defaultButtonText = 'CANCEL';
              const singleButtonWrapperStyle = {}
              let singleButtonWeight = iOSDefaults.button.fontWeight;
              if(index === buttonProps.length - 1){
                  singleButtonWeight = '700';
              }
              if(buttonLayoutHorizontal === 1){
                singleButtonWrapperStyle.minWidth = '50%';
                if(index === 0){
                  singleButtonWrapperStyle.borderStyle = 'solid';
                  singleButtonWrapperStyle.borderRightWidth = 0.55;
                  singleButtonWrapperStyle.borderRightColor = '#dbdbdf';
                }

              }
              return (
                <View style={[styles.iOSButton, singleButtonWrapperStyle]}>
                  <Pressable onPress={() => {
                    props.setModalVisible(false)
                    if(item.func && typeof(item.func) === 'function')
                      item.func();
                  }}>
                    <View style={[styles.iOSButtonInner, {backgroundColor: (item.styles && item.styles.backgroundColor) || iOSDefaults.button.backgroundColor}]}>
                      <Text
                        style={{
                          color: (item.styles && item.styles.color) || iOSDefaults.button.color,
                          fontFamily: (item.styles && item.styles.fontFamily) || iOSDefaults.button.fontFamily,
                          fontSize: (item.styles && item.styles.fontSize) || iOSDefaults.button.fontSize,
                          fontWeight: (item.styles && item.styles.fontWeight) || singleButtonWeight,
                          textTransform: (item.styles && item.styles.textTransform) || iOSDefaults.button.textTransform,
                          textAlign: 'center'
                        }}
                      >{item.text || defaultButtonText}</Text>
                    </View>
                  </Pressable>
                </View>
              )
            })

        }
      </View>
    );
  }
  return (
    <Modal
        animationType="fade"
        transparent={true}
        visible={props.modalVisible}
        onRequestClose={() => {
          props.setModalVisible(false);
        }}
      >
        <Pressable style={[Platform.OS === "ios" ? styles.iOSBackdrop : styles.androidBackdrop, styles.backdrop]} onPress={() => props.setModalVisible(false)} />
        <View style={styles.alertBox}>
        {
          Platform.OS === "ios" ? 
          <View style={[styles.iOSAlertBox, iOSDefaults.container]}>
            <Text style={[styles.iOSTitle, iOSDefaults.title]}>{props.title || 'Message'}</Text>
            <Text style={[styles.iOSMessage, iOSDefaults.message]}>{props.message || ''}</Text>
            <IOSButtonBox />
          </View>
          :
          <View style={[styles.androidAlertBox, androidDefaults.container]}>
            <Text style={[styles.androidTitle, androidDefaults.title]}>{props.title || 'Message'}</Text>
            <Text style={[styles.androidMessage, androidDefaults.message]}>{props.message || ''}</Text>
            <AndroidButtonBox />
          </View>
        }
        </View>


      </Modal>
  )
}
const App = () => {
  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View style={styles.centeredView}>
      <CustomAlert 
          modalVisible={modalVisible} 
          setModalVisible={setModalVisible}
          title={'Alert Title'}
          message={'This is some message'} 
          android={{
            container: {
              backgroundColor: 'yellow'
            },
            title: {
              color: 'red',
              fontFamily: 'Roboto',
              fontSize: 26,
              fontWeight: 'regular',
            },
            message: {
              color: 'blue',
              fontFamily: 'Roboto',
              fontSize: 16,
              fontWeight: 'regular',
            },
          }}
          ios={{
            container: {
              backgroundColor: 'yellow'
            },
            title: {
              color: 'red',
              fontFamily: 'Roboto',
              fontSize: 26,
              fontWeight: 'regular',
            },
            message: {
              color: 'blue',
              fontFamily: 'Roboto',
              fontSize: 16,
              fontWeight: 'regular',
            },
          }}
          buttons={[{
            text: 'no'
          },{
            text: 'Yes',
            func: () => {console.log('Yes Pressed')},
            styles: {
              color: '#FFFFFF',
              fontSize: 18,
              fontWeight: 'bold',
              fontFamily: 'Roboto',
              textTransform: 'none',
              backgroundColor: '#000000'
            }
          }]}
      />
      <View>
        <Pressable
          style={[styles.button, styles.buttonOpen]}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.textStyle}>Show Modal</Text>
        </Pressable>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },

  button: {
    borderRadius: 20,
    padding: 10,
    elevation: 2
  },
  buttonOpen: {
    backgroundColor: "#F194FF",
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },

  iOSBackdrop: {
    backgroundColor: "#000000",
    opacity: 0.3
  },
  androidBackdrop: {
    backgroundColor: "#232f34",
    opacity: 0.4
  },
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0
  },
  alertBox: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  androidAlertBox: {
    maxWidth: 280,
    width: '100%',
    margin: 48,
    elevation: 24,
    borderRadius: 2,
  },
  androidTitle: {
    margin: 24,
  },
  androidMessage: {
    marginLeft: 24,
    marginRight: 24,
    marginBottom: 24,
  },
  androidButtonGroup: {
    marginTop: 0,
    marginRight: 0,
    marginBottom: 8,
    marginLeft: 24,
  },
  androidButton: {
    marginTop: 12,
    marginRight: 8,    
  },
  androidButtonInner: {
    padding: 10,

  },

  iOSAlertBox: {
    maxWidth: 270,
    width: '100%',
    zIndex: 10,
    borderRadius: 13,
  },
  iOSTitle: {
    paddingTop: 12,
    paddingRight: 16,
    paddingBottom: 7,
    paddingLeft: 16,
    marginTop: 8,
    textAlign: "center",
  },
  iOSMessage: {
    paddingTop: 0,
    paddingRight: 16,
    paddingBottom: 21,
    paddingLeft: 16,
    textAlign: "center"
  },
  iOSButtonGroup: {
    marginRight: -0.55
  },
  iOSButton: {

    borderTopColor: '#dbdbdf',
    borderTopWidth: 0.55,
    borderStyle: 'solid',
  },
  iOSButtonInner: {
    minHeight: 44,
    justifyContent: 'center'
  }
});
export default App;

不同的CustomAlert 值的渲染输出是。

Final CustomAlert Outputs RN

现场演示

总结

在这篇文章中,我们了解了Android和iOS中的警报对话框。我们还看到了它们的用户界面、规格和属性之间的区别。

这些警报对话框有明确的目的,不应该被过度使用。只有当信息对用户来说是不可避免的时候才使用它们,因为它们会阻塞用户界面。

在我们的自定义警报中,有很多增强的范围。你可以改变填充物、边距、添加背景图片、图标、SVG等等。如果你在你的项目中试用这段代码,那么请通过评论让我知道它是如何工作的。谢谢你。

The postHow to create a custom alert dialog in React Nativeappeared first onLogRocket Blog.