如何用一个按钮关闭React Native模态

525 阅读5分钟

如何用一个按钮关闭React Native模态

最近我一直在使用React Native,我遇到了一些未实现的功能,我需要将其添加到基本组件中。其中一个组件是React NativeModal 组件,用于在另一个视图之上显示一个视图。它没有承诺更多的功能,但它对实现你自己的弹出式窗口非常有用。

另一方面,由于它只是一个简单的模态,它不包括你对一个弹出式组件所期望的一切,比如一个X来关闭它,或者当你点击旁边的模态时关闭它。当你从一个模态 组件开始时,我将指导你如何添加这两个功能。

如何关闭一个模态

让我们从一个基本的App.js 开始,显示一个弹出式窗口。我还添加了一些样式,让它有一个阴影,这样弹出式窗口就可以看到了,还添加了大量的空白,这样我们就可以在以后通过点击旁边的弹出式窗口来测试关闭它。

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

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);

  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</text>
      <Modal visible={modalVisible}>
        <View style={styles.modal}>
          <Text>
            Popup content.
          </Text>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
});

Modal 是根据它的可见道具来显示的。现在,状态中的值始终为真,所以弹出窗口将始终是可见的。

即使没有按钮或其他改变状态的方法,在Android上也可以用后退按钮关闭Modal (根据文档,Apple TV上的菜单按钮应该有同样的效果,但我没有一个可以测试)。

按下按钮会调用modal的onRequestClose道具中指定的函数,所以让我们改变状态,在按下按钮时隐藏弹出窗口,这将显示应用程序的主屏幕。

<Modal
  visible={modalVisible}
  onRequestClose={() => setModalVisible(false)}>

如何在React Native模态中添加关闭按钮

首先,我们必须添加按钮本身,这样我们就可以用它来关闭弹出窗口。在这种情况下,我想在弹出窗口的右上角添加一个小X,但它也可以在其他地方。

考虑到定位的工作方式,对此有两种选择:

  • 定位绝对是把按钮放在右上角。然后你可以对其余的内容做任何你想做的事,但这样你就有可能让内容与X重叠,因为它超出了正常的布局流程。如果你想同时使用按钮旁边和下面的空间,这几乎是不可能的,这就导致我们有了第二个选择。
  • 用flexbox定位按钮,在按钮的左边留出空间做标题。然后你可以分别填入标题和底部的内容。如果你要做的是一个弹出式的东西,有一个标题也是一个很标准的功能。

下面是添加了X的组件现在的样子:

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

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);

  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Modal visible={modalVisible} onRequestClose={() => setModalVisible(false)}>
        <View style={styles.modal}>
          <View style={styles.modalHeader}>
            <View style={styles.modalHeaderContent}><Text>Other header content</Text></View>
            <TouchableOpacity><Text style={styles.modalHeaderCloseText}>X</Text></TouchableOpacity>
          </View>
          <View style={styles.modalContent}>
            <Text>
              Popup content.
            </Text>
          </View>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  /* The content of the modal takes all the vertical space not used by the header. */
  modalContent: {
    flex: 1
  },
  modalHeader: {
    flexDirection: "row",
  },
  /* The header takes up all the vertical space not used by the close button. */
  modalHeaderContent: {
    flexGrow: 1,
  },
  modalHeaderCloseText: {
    textAlign: "center",
    paddingLeft: 5,
    paddingRight: 5
  }
});

标题是一个灵活的容器,显示为一排,用flexGrow:1 ,表示它应该占据所有的剩余空间。

弹出窗口中的其他内容是一个flex:1 ,所以它占据了所有剩余的高度。

在这一点上,唯一剩下的就是把按钮连接起来,让它关闭弹出窗口,就像我们之前在onRequestClose 事件中设置的那样。

<TouchableOpacity onPress={() => setModalVisible(false)}>
  <Text style={styles.modalHeaderCloseText}>X</Text>
</TouchableOpacity>

如何通过点击外部来关闭模态

为了通过点击外部来关闭模态,我们需要一个额外的组件来捕捉这些点击。

另一方面,我们不希望这个组件捕捉为子组件准备的点击:点击弹出窗口本身不应该关闭它。通过检查event.target == event.currentTarget ,我们验证了被选中的项目是组件本身,而不是它的一个子组件。

我使用了一个Pressable组件,因为我不希望在触碰弹出式窗口之外时,出现TouchableOpacity所带来的 "调光 "效果。这个新的Pressable 组件包裹了我们之前在modal中定义的所有组件。

这是完成的弹出窗口,有一些额外的边框来显示标题和弹出窗口的内容。

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

export default function App() {
  const [modalVisible, setModalVisible] = useState(true);
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Modal
        visible={modalVisible}
        onRequestClose={() => setModalVisible(false)}>
        <Pressable style={styles.outsideModal}
          onPress={(event) => { if (event.target == event.currentTarget) { 
            setModalVisible(false); } }} >
          <View style={styles.modal}>
            <View style={styles.modalHeader}>
              <View style={styles.modalHeaderContent}><Text>Other header content</Text></View>
              <TouchableOpacity onPress={() => setModalVisible(false)}>
                <Text style={styles.modalHeaderCloseText}>X</Text>
              </TouchableOpacity>
            </View>
            <View style={styles.modalContent}>
              <Text>
                Popup content.
              </Text>
            </View>
          </View>
        </Pressable>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    flex: 1,
    margin: 50,
    padding: 5,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  /* The content of the modal takes all the vertical space not used by the header. */
  modalContent: {
    flex: 1,
    borderWidth: 1,
    borderColor: "black"
  },
  modalHeader: {
    flexDirection: "row",
    borderWidth: 1,
    borderColor: "black"
  },
  /* The header takes up all the vertical space not used by the close button. */
  modalHeaderContent: {
    flexGrow: 1,
  },
  modalHeaderCloseText: {
    textAlign: "center",
    paddingLeft: 5,
    paddingRight: 5
  },
  outsideModal: {
    backgroundColor: "rgba(1, 1, 1, 0.2)",
    flex: 1,
  }
});

请注意,这有一个小小的限制:PressableTouchableOpacity 的背景颜色不能被设置为透明或半透明的值,所以弹出窗口下的内容将不再可见。

下一步要让这个问题变得更好,就是把Modal 组件和它的所有内容打包成一个新的组件,可以在你的应用程序中重复使用,这样你就可以在弹出窗口中插入任何标题或内容,但这不在本文的范围之内。