首页指南参考教程

创建模态

在本教程中,了解如何从 React Native 创建模式来选择图片。


React Native 提供了 <Modal> 组件,将内容渲染在应用的其余部分之上。一般来说,模态框用于吸引用户对关键信息的注意或指导他们采取行动。例如,在 第二章 中,我们使用 alert() 在按下按钮时显示占位符。这就是模态组件显示叠加层的方式。

¥React Native provides a <Modal> component that presents content above the rest of your app. In general, modals are used to draw a user's attention toward critical information or guide them to take action. For example, in the second chapter, we used alert() to display a placeholder when a button is pressed. That's how a modal component displays an overlay.

在本章中,我们将创建一个显示表情符号选择器列表的模式。

¥In this chapter, we'll create a modal that shows an emoji picker list.

1

声明一个状态变量来显示按钮

¥Declare a state variable to show buttons

在实现模式之前,我们将添加三个新按钮。仅当用户从媒体库中选取图片或决定使用占位符图片时,这些按钮才会可见。这些按钮之一将触发表情符号选择器模式。

¥Before implementing the modal, we are going to add three new buttons. These buttons will only be visible when the user picks an image from the media library or decides to use the placeholder image. One of these buttons will trigger the emoji picker modal.

在 App.js 中声明一个名为 showAppOptions 的状态变量。我们将使用此变量来显示或隐藏打开模式的按钮以及其他一些选项。

¥Declare a state variable called showAppOptions in App.js. We'll use this variable to show or hide buttons that open the modal alongside a few other options.

该变量是一个布尔值。当应用屏幕加载时,我们将其设置为 false,以便在选择图片之前不会显示选项。

¥This variable is a boolean. When the app screen loads, we'll set it to false so that the options are not shown before picking an image.

App.js
export default function App() {
  const [showAppOptions, setShowAppOptions] = useState(false);
  // ...rest of the code remains same
}

当用户从媒体库中选取图片或决定使用占位符图片时,此变量的值将设置为 true

¥The value of this variable will be set to true when the user picks an image from the media library or decides to use the placeholder image.

接下来,修改 pickImageAsync() 函数,在用户选取图片后将 showAppOptions 的值设置为 true

¥Next, modify the pickImageAsync() function to set the value of showAppOptions to true after the user picks an image.

App.js
const pickImageAsync = async () => {
  // ...rest of the code remains same

  if (!result.canceled) {
    setSelectedImage(result.assets[0].uri);
    setShowAppOptions(true);

  } else {
    // ...rest of the code remains same
  }
};

然后,通过添加具有以下值的 onPress 属性来更新没有主题的按钮:

¥Then, update the button with no theme by adding an onPress prop with the following value:

App.js
<Button label="Use this photo" onPress={() => setShowAppOptions(true)} />

现在,我们可以在 Button.js 中渲染第二个按钮时删除 <Button> 组件上的 alert 并更新 onPress 属性:

¥Now, we can remove the alert on the <Button> component and update the onPress prop when rendering the second button in Button.js:

Button.js
<Pressable style={styles.button} onPress={onPress} >

接下来,更新 App.js 以根据 showAppOptions 的值有条件地渲染 <Button> 组件。另外,移动条件运算符块中的按钮。

¥Next, update App.js to conditionally render the <Button> component based on the value of showAppOptions. Also, move the buttons in the conditional operator block.

App.js
export default function App() {
  // ...
  return (
    <View style={styles.container}>
      

{/* ...rest of the code remains same */}


      {showAppOptions ? (
        <View />
      ) : (
        <View style={styles.footerContainer}>
          <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
          <Button label="Use this photo" onPress={() => setShowAppOptions(true)} />
        </View>
      )}
      <StatusBar style="auto" />
    </View>
  );
}

现在,当 showAppOptions 的值为 true 时,让我们渲染一个空的 <View> 组件。我们将在下一步中解决此状态。

¥For now, when the value of showAppOptions is true, let's render an empty <View> component. We'll address this state in the next step.

2

添加按钮

¥Add buttons

让我们分解一下我们将在本章中实现的选项按钮的布局。设计如下:

¥Let's break down the layout of the option buttons we will implement in this chapter. The design looks like this:

它包含一个父级 <View>,其中三个按钮排成一行。中间带有加号图标 (+) 的按钮将打开模式,其样式与其他两个按钮不同。

¥It contains a parent <View> with three buttons aligned in a row. The button in the middle with the plus icon (+) will open the modal and is styled differently than the other two buttons.

在组件目录中,使用以下代码创建一个名为 CircleButton.js 的新文件:

¥Inside the components directory, create a new file called CircleButton.js with the following code:

CircleButton.js
import { View, Pressable, StyleSheet } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function CircleButton({ onPress }) {
  return (
    <View style={styles.circleButtonContainer}>
      <Pressable style={styles.circleButton} onPress={onPress}>
        <MaterialIcons name="add" size={38} color="#25292e" />
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  circleButtonContainer: {
    width: 84,
    height: 84,
    marginHorizontal: 60,
    borderWidth: 4,
    borderColor: '#ffd33d',
    borderRadius: 42,
    padding: 3,
  },
  circleButton: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 42,
    backgroundColor: '#fff',
  },
});

为了渲染加号图标,此按钮使用 @expo/vector-icons 库中的 <MaterialIcons> 图标集。

¥To render the plus icon, this button uses the <MaterialIcons> icon set from the @expo/vector-icons library.

另外两个按钮也使用 <MaterialIcons> 显示垂直对齐的文本标签和图标。接下来,在组件目录中创建一个名为 IconButton.js 的文件。该组件接受三个 props:

¥The other two buttons also use <MaterialIcons> to display vertically aligned text labels and icons. Next, create a file named IconButton.js inside the components directory. This component accepts three props:

  • icon:与 MaterialIcons 库中的图标相对应的名称。

    ¥icon: the name that corresponds to the icon in the MaterialIcons library.

  • label:按钮上显示的文本标签。

    ¥label: the text label displayed on the button.

  • onPress:按下按钮时调用的函数。

    ¥onPress: the function called when the button is pressed.

IconButton.js
import { Pressable, StyleSheet, Text } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function IconButton({ icon, label, onPress }) {
  return (
    <Pressable style={styles.iconButton} onPress={onPress}>
      <MaterialIcons name={icon} size={24} color="#fff" />
      <Text style={styles.iconButtonLabel}>{label}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  iconButton: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  iconButtonLabel: {
    color: '#fff',
    marginTop: 12,
  },
});

将这些按钮导入到 App.js 中,并替换上一步中的空 <View> 组件以显示它们。我们还为这些按钮创建 onPress 函数,以便稍后添加该功能。

¥Import these buttons into App.js and replace the empty <View> component from the previous step to display them. Let's also create the onPress functions for these buttons to add the functionality later.

Add Button options
// ... rest of the import statements
import CircleButton from './components/CircleButton';
import IconButton from './components/IconButton';

export default function App() {
  // ...rest of the code remains same
  const onReset = () => {
    setShowAppOptions(false);
  };

  const onAddSticker = () => {
    // we will implement this later
  };

  const onSaveImageAsync = async () => {
    // we will implement this later
  };

  return (
    <View style={styles.container}>
      

{/* ...rest of the code remains same */}


      {showAppOptions ? (
        <View style={styles.optionsContainer}>
          <View style={styles.optionsRow}>
            <IconButton icon="refresh" label="Reset" onPress={onReset} />
            <CircleButton onPress={onAddSticker} />
            <IconButton icon="save-alt" label="Save" onPress={onSaveImageAsync} />
          </View>
        </View>
      ) : (
        // ...rest of the code remains same
      )}
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  // ...previous styles remain unchanged
  optionsContainer: {
    position: 'absolute',
    bottom: 80,
  },
  optionsRow: {
    alignItems: 'center',
    flexDirection: 'row',
  },
})

在上面的代码片段中,当用户按下重置按钮时,将调用 onReset() 函数。按下此按钮后,我们将再次显示图片选择器按钮。

¥In the above snippet, the onReset() function is called when the user presses the reset button. When this button is pressed, we'll show the image picker button again.

让我们看看我们在 Android、iOS 和 Web 上的应用:

¥Let's take a look at our app on Android, iOS and the web:

3

创建表情符号选择器模式

¥Create an emoji picker modal

该模式允许用户从可用表情符号列表中选择表情符号。在组件目录中创建 EmojiPicker.js 文件。该组件接受三个 props:

¥The modal allows the user to choose an emoji from a list of available emoji. Create an EmojiPicker.js file inside the components directory. This component accepts three props:

  • isVisible:一个布尔值,用于确定模式是否可见。

    ¥isVisible: a boolean that determines whether the modal is visible or not.

  • onClose:关闭模态框的函数。

    ¥onClose: a function that closes the modal.

  • children:稍后用于显示表情符号列表。

    ¥children: used later to display a list of emoji.

EmojiPicker.js
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function EmojiPicker({ isVisible, children, onClose }) {
  return (
    <Modal animationType="slide" transparent={true} visible={isVisible}>
      <View style={styles.modalContent}>
        <View style={styles.titleContainer}>
          <Text style={styles.title}>Choose a sticker</Text>
          <Pressable onPress={onClose}>
            <MaterialIcons name="close" color="#fff" size={22} />
          </Pressable>
        </View>
        {children}
      </View>
    </Modal>
  );
}

我们来了解一下上面的代码做了什么。

¥Let's learn what the above code does.

  • <Modal> 组件显示标题和关闭按钮。

    ¥The <Modal> component displays a title and a close button.

  • 它的 visible 属性采用 isVisible 的值并控制模式是打开还是关闭。

    ¥Its visible prop takes the value of isVisible and controls if the modal is open or closed.

  • 它的 transparent 属性是一个布尔值,决定模态是否填充整个视图。

    ¥Its transparent prop is a boolean value that determines whether the modal fills the entire view.

  • 它的 animationType 属性决定了它如何进入和离开屏幕。在本例中,它是从屏幕底部滑动的。

    ¥Its animationType prop determines how it enters and leaves the screen. In this case, it is sliding from the bottom of the screen.

  • 最后,当用户按下关闭 <Pressable> 时,会调用 <EmojiPicker> onClose 属性。

    ¥Lastly, the <EmojiPicker> onClose prop is called when the user presses the close <Pressable>.

下一步是为 <EmojiPicker> 组件添加相应的样式:

¥The next step is to add the corresponding styles for the <EmojiPicker> component:

EmojiPicker.js
const styles = StyleSheet.create({
  modalContent: {
    height: '25%',
    width: '100%',
    backgroundColor: '#25292e',
    borderTopRightRadius: 18,
    borderTopLeftRadius: 18,
    position: 'absolute',
    bottom: 0,
  },
  titleContainer: {
    height: '16%',
    backgroundColor: '#464C55',
    borderTopRightRadius: 10,
    borderTopLeftRadius: 10,
    paddingHorizontal: 20,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  title: {
    color: '#fff',
    fontSize: 16,
  },
});

现在,我们将 App.js 修改为:

¥Now, let's modify the App.js to:

  • 导入 <EmojiPicker> 组件。

    ¥Import the <EmojiPicker> component.

  • 然后,使用 useState 钩子创建 isModalVisible 状态变量。它有一个默认值 false,以确保模式处于隐藏状态,直到用户按下按钮将其打开。

    ¥Then, create an isModalVisible state variable with the useState hook. It has a default value of false to ensure that the modal is hidden until the user presses the button to open it.

  • 替换 onAddSticker() 函数中的注释,以便在用户按下按钮时将 isModalVisible 变量更新为 true。这将打开表情符号选择器。

    ¥Replace the comment in the onAddSticker() function to update the isModalVisible variable to true when the user presses the button. This will open the emoji picker.

  • 创建 aonModalClose() 函数来更新 isModalVisible 状态变量。

    ¥Create aonModalClose() function to update the isModalVisible state variable.

  • <EmojiPicker> 组件放置在 <App> 组件的底部、<StatusBar> 组件的上方。

    ¥Place the <EmojiPicker> component at the bottom of the <App> component, above the <StatusBar> component.

Create a modal
// ...rest of the import statements remain same
import EmojiPicker from "./components/EmojiPicker";

export default function App() {
  const [isModalVisible, setIsModalVisible] = useState(false);
  // ...rest of the code remains same

  const onAddSticker = () => {
    setIsModalVisible(true);
  };

  const onModalClose = () => {
    setIsModalVisible(false);
  };

  return (
    <View style={styles.container}>
      

{/* ...rest of the code remains same */}


      <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
        

{/* A list of emoji component will go here */}


      </EmojiPicker>
      <StatusBar style="auto" />
    </View>
  );
}

这是这一步之后的结果:

¥Here is the result after this step:

4

显示表情符号列表

¥Display a list of emoji

让我们在模式内容中实现表情符号的水平列表。我们将使用 React Native 中的 <FlatList> 组件。

¥Let's implement a horizontal list of emoji in the modal's content. We'll use the <FlatList> component from React Native for it.

在组件目录下创建一个名为 EmojiList.js 的文件,并添加以下代码:

¥Create a file named EmojiList.js file in the components directory and add the following code:

EmojiList.js
import { useState } from 'react';
import { StyleSheet, FlatList, Image, Platform, Pressable } from 'react-native';

export default function EmojiList({ onSelect, onCloseModal }) {
  const [emoji] = useState([
    require('../assets/images/emoji1.png'),
    require('../assets/images/emoji2.png'),
    require('../assets/images/emoji3.png'),
    require('../assets/images/emoji4.png'),
    require('../assets/images/emoji5.png'),
    require('../assets/images/emoji6.png'),
  ]);

  return (
    <FlatList
      horizontal
      showsHorizontalScrollIndicator={Platform.OS === 'web'}
      data={emoji}
      contentContainerStyle={styles.listContainer}
      renderItem={({ item, index }) => (
        <Pressable
          onPress={() => {
            onSelect(item);
            onCloseModal();
          }}>
          <Image source={item} key={index} style={styles.image} />
        </Pressable>
      )}
    />
  );
}

const styles = StyleSheet.create({
  listContainer: {
    borderTopRightRadius: 10,
    borderTopLeftRadius: 10,
    paddingHorizontal: 20,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  image: {
    width: 100,
    height: 100,
    marginRight: 20,
  },
});

上面的 <FlatList> 组件使用 <Image> 组件(封装着 <Pressable> 组件)渲染所有表情符号图片。稍后,我们将对其进行改进,以便用户可以点击屏幕上的表情符号,使其显示为图片上的贴纸。

¥The <FlatList> component above renders all the emoji images using a <Image> component wrapped with a <Pressable> component. Later, we will improve it so that the user can tap an emoji on the screen to make it appear as a sticker on the image.

<FlatList> 组件采用一个项目数组,在上面的代码片段中,该数组由 emoji 数组变量提供作为 data 属性的值。然后,renderItem 属性从 data 获取该项目并返回列表中的项目。最后,我们添加 <Image><Pressable> 组件来显示该项目。

¥The <FlatList> component takes an array of items, which in the above snippet is provided by the emoji array variable as the value of the data prop. Then, the renderItem prop takes the item from the data and returns the item in the list. Finally, we add <Image> and the <Pressable> components to display this item.

horizontal 属性水平渲染列表而不是垂直渲染。showsHorizontalScrollIndicator 使用 React Native 的 Platform 模块检查平台,并仅在 Web 上显示水平滚动条。

¥The horizontal prop renders the list horizontally instead of vertically. The showsHorizontalScrollIndicator checks the platform using Platform module from React Native and displays the horizontal scroll bar only on the web.

现在,修改 App 组件。导入 <EmojiList> 组件,并将使用 <EmojiPicker> 组件的注释替换为以下代码片段:

¥Now, modify the App component. Import the <EmojiList> component and replace the comments where the <EmojiPicker> component is used with the following code snippet:

App.js
//...rest of the import statements remain same
import EmojiList from './components/EmojiList';

// Inside App component to select the emoji from the list

export default function App() {
  const [pickedEmoji, setPickedEmoji] = useState(null);
  // ...rest of the code remain same

  return (
    <View style={styles.container}>
      

{/* rest of the code remains unchanged */}


      <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
        <EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} />
      </EmojiPicker>
      <StatusBar style="auto" />
    </View>
  );
}

<EmojiList> 组件上的 onSelect 属性选择表情符号,而 onCloseModal 属性在选择表情符号后关闭模式。

¥The onSelect prop on the <EmojiList> component selects the emoji and the onCloseModal prop closes the modal after emoji is selected.

让我们看看我们在 Android、iOS 和 Web 上的应用:

¥Let's take a look at our app on Android, iOS and the web:

5

显示选定的表情符号

¥Display the selected emoji

现在我们将表情符号贴纸放在图片上。

¥Now we'll put the emoji sticker on the image.

首先在组件目录中创建一个新文件并将其命名为 EmojiSticker.js。然后,添加以下代码:

¥Start by creating a new file in the components directory and call it EmojiSticker.js. Then, add the following code:

EmojiSticker.js
import { View, Image } from 'react-native';

export default function EmojiSticker({ imageSize, stickerSource }) {
  return (
    <View style={{ top: -350 }}>
      <Image
        source={stickerSource}
        resizeMode="contain"
        style={{ width: imageSize, height: imageSize }}
      />
    </View>
  );
}

该组件接收两个 props:

¥This component receives two props:

  • imageSize<App> 组件内部定义的值。我们将在下一章中使用这个值来缩放点击时图片的大小。

    ¥imageSize: a value defined inside the <App> component. We will use this value in the next chapter to scale the image's size when tapped.

  • stickerSource:所选表情符号图片的来源。

    ¥stickerSource: the source of the selected emoji image.

我们将在 App.js 文件中导入此组件,并更新 <App> 组件以有条件地在图片上显示表情符号贴纸。我们将通过检查 pickedEmoji 状态是否不是 null 来完成此操作。

¥We'll import this component in the App.js file and update the <App> component to display the emoji sticker on the image conditionally. We'll do this by checking if the pickedEmoji state is not null.

Display selected emoji sticker
// ...rest of the import statements
import EmojiSticker from './components/EmojiSticker';

export default function App() {
  // ...rest of the code remains same

  return (
    <View>
      <View style={styles.imageContainer}>
        <ImageViewer placeholderImageSource={PlaceholderImage} selectedImage={selectedImage} />
        {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />}
      </View>
      

{/* ...rest of the code remains same */}


    </View>
  );
}

让我们看看我们在 Android、iOS 和 Web 上的应用:

¥Let's take a look at our app on Android, iOS and the web:

下一步

¥Next step

我们成功创建了表情符号选择器模式,并实现了选择表情符号并将其显示在图片上的逻辑。

¥We successfully created the emoji picker modal and implemented the logic to select an emoji and display it over the image.

添加手势

在下一章中,我们将通过手势添加用户交互,以拖动表情符号并通过点击来缩放大小。

Expo 中文网 - 粤ICP备13048890号