使用图片选择器

在本教程中,学习如何使用 Expo Image Picker。


React Native 提供内置组件作为标准构建块,例如 <View><Text><Pressable>。我们正在构建一个从设备的媒体库中选择图片的功能。核心组件无法做到这一点,我们需要一个库来在我们的应用中添加此功能。

¥React Native provides built-in components as standard building blocks, such as <View>, <Text>, and <Pressable>. We are building a feature to select an image from the device's media gallery. This isn't possible with the core components and we'll need a library to add this feature in our app.

我们将使用 expo-image-picker,这是 Expo SDK 中的一个库。

¥We'll use expo-image-picker, a library from Expo SDK.

expo-image-picker 提供对系统 UI 的访问,以从手机库中选择图片和视频。

¥expo-image-picker provides access to the system's UI to select images and videos from the phone's library.

Watch: Using an image picker in your universal Expo app
Watch: Using an image picker in your universal Expo app

1

安装 expo-image-picker

¥Install expo-image-picker

要安装该库,请运行以下命令:

¥To install the library, run the following command:

Terminal
npx expo install expo-image-picker
提示:每当我们在项目中安装新库时,请通过在终端中按 Ctrl + c 来停止开发服务器,然后运行安装命令。安装完成后,我们可以通过从同一终端窗口运行 npx expo start 来重新启动开发服务器。

2

从设备的媒体库中选择图片

¥Pick an image from the device's media library

expo-image-picker 提供 launchImageLibraryAsync() 方法,通过从设备的媒体库中选择图片或视频来显示系统 UI。我们将使用上一章中创建的主要主题按钮从设备的媒体库中选择图片,并创建一个函数来启动设备的图片库以实现此功能。

¥expo-image-picker provides launchImageLibraryAsync() method to display the system UI by choosing an image or a video from the device's media library. We'll use the primary themed button created in the previous chapter to select an image from the device's media library and create a function to launch the device's image library to implement this functionality.

在 app/(tabs)/index.tsx 中,导入 expo-image-picker 库并在 Index 组件内创建 pickImageAsync() 函数:

¥In app/(tabs)/index.tsx, import expo-image-picker library and create a pickImageAsync() function inside the Index component:

app/(tabs)/index.tsx
// ...rest of the import statements remain unchanged
import * as ImagePicker from 'expo-image-picker';

export default function Index() {
const pickImageAsync = async () => {
  let result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ['images'],
    allowsEditing: true,
    quality: 1,
  });

  if (!result.canceled) {
    console.log(result);
  } else {
    alert('You did not select any image.');
  }
};

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

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

¥Let's learn what the above code does:

  • launchImageLibraryAsync() 接收一个对象以指定不同的选项。此对象是 ImagePickerOptions 对象,我们在调用该方法时传递它。

    ¥The launchImageLibraryAsync() receives an object to specify different options. This object is the ImagePickerOptions object, which we are passing when invoking the method.

  • allowsEditing 设置为 true 时,用户可以在 Android 和 iOS 上的选择过程中裁剪图片。

    ¥When allowsEditing is set to true, the user can crop the image during the selection process on Android and iOS.

3

更新按钮组件

¥Update the button component

按下主按钮时,我们将在 Button 组件上调用 pickImageAsync() 函数。更新 components/Button.tsx 中 Button 组件的 onPress prop:

¥On pressing the primary button, we'll call the pickImageAsync() function on the Button component. Update the onPress prop of the Button component in components/Button.tsx:

components/Button.tsx
import { StyleSheet, View, Pressable, Text } from 'react-native';
import FontAwesome from '@expo/vector-icons/FontAwesome';

type Props = {
label: string;
theme?: 'primary';
onPress?: () => void;
};

export default function Button({ label, theme, onPress }: Props) {
if (theme === 'primary') {
  return (
    <View
      style={[
        styles.buttonContainer,
        { borderWidth: 4, borderColor: '#ffd33d', borderRadius: 18 },
      ]}>
      <Pressable style={[styles.button, { backgroundColor: '#fff' }]} onPress={onPress}>
        <FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} />
        <Text style={[styles.buttonLabel, { color: '#25292e' }]}>{label}</Text>
      </Pressable>
    </View>
  );
}

return (
  <View style={styles.buttonContainer}>
    <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}>
      <Text style={styles.buttonLabel}>{label}</Text>
    </Pressable>
  </View>
);
}

const styles = StyleSheet.create({
buttonContainer: {
  width: 320,
  height: 68,
  marginHorizontal: 20,
  alignItems: 'center',
  justifyContent: 'center',
  padding: 3,
},
button: {
  borderRadius: 10,
  width: '100%',
  height: '100%',
  alignItems: 'center',
  justifyContent: 'center',
  flexDirection: 'row',
},
buttonIcon: {
  paddingRight: 8,
},
buttonLabel: {
  color: '#fff',
  fontSize: 16,
},
});

在 app/(tabs)/index.tsx 中,将 pickImageAsync() 函数添加到第一个 <Button> 上的 onPress prop。

¥In app/(tabs)/index.tsx, add the pickImageAsync() function to the onPress prop on the first <Button>.

app/(tabs)/index.tsx
import { View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';

import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';

const PlaceholderImage = require('@/assets/images/background-image.png');

export default function Index() {
const pickImageAsync = async () => {
  let result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ['images'],
    allowsEditing: true,
    quality: 1,
  });

  if (!result.canceled) {
    console.log(result);
  } else {
    alert('You did not select any image.');
  }
};

return (
  <View style={styles.container}>
    <View style={styles.imageContainer}>
      <ImageViewer imgSource={PlaceholderImage} />
    </View>
    <View style={styles.footerContainer}>
      <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
      <Button label="Use this photo" />
    </View>
  </View>
);
}

const styles = StyleSheet.create({
container: {
  flex: 1,
  backgroundColor: '#25292e',
  alignItems: 'center',
},
imageContainer: {
  flex: 1,
},
footerContainer: {
  flex: 1 / 3,
  alignItems: 'center',
},
});

pickImageAsync() 函数调用 ImagePicker.launchImageLibraryAsync(),然后处理结果。launchImageLibraryAsync() 方法返回一个对象,其中包含有关所选图片的信息。

¥The pickImageAsync() function invokes ImagePicker.launchImageLibraryAsync() and then handles the result. The launchImageLibraryAsync() method returns an object containing information about the selected image.

以下是 result 对象及其包含的属性的示例:

¥Here is an example of the result object and the properties it contains:

{
"assets": [
  {
    "assetId": null,
    "base64": null,
    "duration": null,
    "exif": null,
    "fileName": "ea574eaa-f332-44a7-85b7-99704c22b402.jpeg",
    "fileSize": 4513577,
    "height": 4570,
    "mimeType": "image/jpeg",
    "rotation": null,
    "type": "image",
    "uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FStickerSmash-13f21121-fc9d-4ec6-bf89-bf7d6165eb69/ImagePicker/ea574eaa-f332-44a7-85b7-99704c22b402.jpeg",
    "width": 2854
  }
],
"canceled": false
}
{
"assets": [
  {
    "assetId": "99D53A1F-FEEF-40E1-8BB3-7DD55A43C8B7/L0/001",
    "base64": null,
    "duration": null,
    "exif": null,
    "fileName": "IMG_0004.JPG",
    "fileSize": 2548364,
    "height": 1669,
    "mimeType": "image/jpeg",
    "type": "image",
    "uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FStickerSmash-13f21121-fc9d-4ec6-bf89-bf7d6165eb69/ImagePicker/ea574eaa-f332-44a7-85b7-99704c22b402.jpeg",
    "width": 1668
  }
],
"canceled": false
}
{
"assets": [
  {
    "fileName": "some-image.png",
    "height": 720,
    "mimeType": "image/png",
    "uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAA"
  }
],
"canceled": false
}

4

使用选定的图片

¥Use the selected image

result 对象提供 assets 数组,其中包含所选图片的 uri。让我们从图片选择器中获取该值,并使用它在应用中显示选定的图片。

¥The result object provides the assets array, which contains the uri of the selected image. Let's take this value from the image picker and use it to show the selected image in the app.

修改 app/(tabs)/index.tsx 文件:

¥Modify the app/(tabs)/index.tsx file:

  1. 使用 React 中的 useState 钩子声明一个名为 selectedImage 的状态变量。我们将使用此状态变量来保存所选图片的 URI。

    ¥Declare a state variable called selectedImage using the useState hook from React. We'll use this state variable to hold the URI of the selected image.

  2. 更新 pickImageAsync() 函数以将图片 URI 保存在 selectedImage 状态变量中。

    ¥Update the pickImageAsync() function to save the image URI in the selectedImage state variable.

  3. selectedImage 作为属性传递给 ImageViewer 组件。

    ¥Pass the selectedImage as a prop to the ImageViewer component.

app/(tabs)/index.tsx
import { View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';

import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';

const PlaceholderImage = require('@/assets/images/background-image.png');

export default function Index() {
const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined);

const pickImageAsync = async () => {
  let result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ['images'],
    allowsEditing: true,
    quality: 1,
  });

  if (!result.canceled) {
    setSelectedImage(result.assets[0].uri);
  } else {
    alert('You did not select any image.');
  }
};

return (
  <View style={styles.container}>
    <View style={styles.imageContainer}>
      <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} />
    </View>
    <View style={styles.footerContainer}>
      <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
      <Button label="Use this photo" />
    </View>
  </View>
);
}

const styles = StyleSheet.create({
container: {
  flex: 1,
  backgroundColor: '#25292e',
  alignItems: 'center',
},
imageContainer: {
  flex: 1,
},
footerContainer: {
  flex: 1 / 3,
  alignItems: 'center',
},
});

selectedImage 属性传递给 ImageViewer 组件以显示选定的图片而不是占位符图片。

¥Pass the selectedImage prop to the ImageViewer component to display the selected image instead of a placeholder image.

  1. 修改 components/ImageViewer.tsx 文件以接受 selectedImage 属性。

    ¥Modify the components/ImageViewer.tsx file to accept the selectedImage prop.

  2. 图片源越来越长,所以我们也将其移动到一个名为 imageSource 的单独变量中。

    ¥The source of the image is getting long, so let's also move it to a separate variable called imageSource.

  3. imageSource 作为 Image 组件上 source 属性的值传递。

    ¥Pass imageSource as the value of the source prop on the Image component.

components/ImageViewer.tsx
import { StyleSheet } from 'react-native';
import { Image, type ImageSource } from 'expo-image';

type Props = {
imgSource: ImageSource;
selectedImage?: string;
};

export default function ImageViewer({ imgSource, selectedImage }: Props) {
const imageSource = selectedImage ? { uri: selectedImage } : imgSource;

return <Image source={imageSource} style={styles.image} />;
}

const styles = StyleSheet.create({
image: {
  width: 320,
  height: 440,
  borderRadius: 18,
},
});

在上面的代码片段中,Image 组件使用条件运算符来加载图片的源。选择的图片是 uri 字符串,而不是像占位符图片这样的本地资源。

¥In the above snippet, the Image component uses a conditional operator to load the image's source. The picked image is a uri string, not a local asset like the placeholder image.

现在让我们看看我们的应用:

¥Let's take a look at our app now:

本教程中示例应用使用的图片是从 未飞溅 中挑选出来的。

¥The images used for the example app in this tutorial were picked from Unsplash.

概括

¥Summary

Chapter 4: Use an image picker

We've successfully added the functionality to pick an image from the device's media library.

In the next chapter, we'll learn how to create an emoji picker modal component.

Next: Create an emoji picker modal