截图

在本教程中,了解如何使用第三方库和 Expo Media Library 捕获屏幕截图。


在本章中,我们将学习如何使用第三方库截图并将其保存到设备的媒体库中。我们将使用 react-native-view-shot 来截图,并使用 expo-media-library 将图片保存到设备的媒体库中。

🌐 In this chapter, we'll learn how to take a screenshot using a third-party library and save it on the device's media library. We'll use react-native-view-shot to take a screenshot and expo-media-library to save an image on device's media library.

信息 到目前为止,我们已经使用了第三方库,例如 react-native-gesture-handlerreact-native-reanimated。根据具体的使用场景,我们可以在 React Native 目录 上找到数百个其他第三方库。

观看:在你的通用 Expo 应用中截屏
观看:在你的通用 Expo 应用中截屏

1

安装库

🌐 Install libraries

要安装 react-native-view-shotexpo-media-library,请运行以下命令:

🌐 To install react-native-view-shot and expo-media-library, run the following commands:

Terminal
npx expo install react-native-view-shot expo-media-library

2

请求权限

🌐 Prompt for permissions

一个需要敏感信息的应用,例如访问设备的媒体库,必须提示权限以允许或拒绝访问。使用来自 expo-media-libraryusePermissions() 钩子,我们可以使用权限 permissionResponserequestPermission() 方法来请求访问。

🌐 An app that requires sensitive information, such as accessing a device's media library, has to prompt permission to allow or deny access. Using usePermissions() hook from expo-media-library, we can use the permission permissionResponse and requestPermission() method to ask for access.

当应用首次加载且权限状态既未授予也未拒绝时,permissionResponse 的值为 null。当请求权限时,用户可以选择授予权限或拒绝权限。我们可以添加一个条件来检查是否未授予权限。如果未授予权限,则触发 requestPermission() 方法。获取权限后,permissionResponse 的值会变为 granted

🌐 When the app loads for the first time and the permission status is neither granted nor denied, the value of the permissionResponse is null. When asked for permission, a user can either grant the permission or deny it. We can add a condition to check if it is not granted. If it is not granted, trigger the requestPermission() method. After getting the access, the value of the permissionResponse changes to granted.

将以下代码片段添加到 app/(tabs)/index.tsx 中:

🌐 Add the following code snippet inside the app/(tabs)/index.tsx:

app/(tabs)/index.tsx
import { useEffect, useState } from 'react'; import * as MediaLibrary from 'expo-media-library'; // ...rest of the code remains same export default function Index() { const [permissionResponse, requestPermission] = MediaLibrary.usePermissions(); // ...rest of the code remains same useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); // ...rest of the code remains same }

3

创建一个引用以保存当前视图

🌐 Create a ref to save the current view

我们将使用 react-native-view-shot 让用户在应用中截图。该库使用 captureRef() 方法将 <View> 的截图捕获为图片。它会返回捕获的截图图片文件的 URI。

🌐 We'll use react-native-view-shot to allow the user to take a screenshot within the app. This library captures the screenshot of a <View> as an image using the captureRef() method. It returns the URI of the captured screenshot image file.

  1. react-native-view-shot 导入 captureRef,并从 React 导入 useRef
  2. 创建一个 imageRef 引用变量来存储截取的屏幕截图图片的引用。
  3. <ImageViewer><EmojiSticker> 组件封装在一个 <View> 中,然后将引用变量传递给它。
app/(tabs)/index.tsx
import { useEffect, useState, useRef } from 'react'; import { captureRef } from 'react-native-view-shot'; export default function Index() { const imageRef = useRef<View>(null); // ...rest of the code remains same return ( <GestureHandlerRootView style={styles.container}> <View style={styles.imageContainer}> <View ref={imageRef} collapsable={false}> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {/* ...rest of the code remains same */} </GestureHandlerRootView> ); }

在上面的代码片段中,collapsable 属性被设置为 false。这使得 <View> 组件只截取背景图片和表情贴纸。

🌐 In the above snippet, the collapsable prop is set to false. This allows the <View> component to screenshot only of the background image and emoji sticker.

4

截图并保存

🌐 Capture a screenshot and save it

我们可以通过在 onSaveImageAsync() 函数中调用 react-native-view-shotcaptureRef() 方法来捕获视图的截图。它接受一个可选参数,我们可以在其中传递截图区域的 widthheight。我们可以在库的文档中了解更多可用选项。

🌐 We can capture a screenshot of the view by calling the captureRef() method from react-native-view-shot inside the onSaveImageAsync() function. It accepts an optional argument where we can pass the width and height of the screenshot capturing area. We can read more about available options in the library's documentation.

captureRef() 方法也会返回一个 promise,该 promise 会在截图的 URI 可用时完成。我们将把这个 URI 作为参数传递给 MediaLibrary.saveToLibraryAsync(),并将截图保存到设备的媒体库中。

🌐 The captureRef() method also returns a promise that fulfills with the screenshot's URI. We will pass this URI as a parameter to MediaLibrary.saveToLibraryAsync() and save the screenshot to the device's media library.

app/(tabs)/index.tsx 中,使用以下代码更新 onSaveImageAsync() 函数:

🌐 Inside app/(tabs)/index.tsx, update the onSaveImageAsync() function with the following code:

app/(tabs)/index.tsx
import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; import { useEffect, useRef, useState } from 'react'; import { ImageSourcePropType, StyleSheet, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { captureRef } from 'react-native-view-shot'; import Button from '@/components/Button'; import CircleButton from '@/components/CircleButton'; import EmojiList from '@/components/EmojiList'; import EmojiPicker from '@/components/EmojiPicker'; import IconButton from '@/components/IconButton'; import ImageViewer from '@/components/ImageViewer'; import EmojiSticker from '@/components/EmojiSticker'; const PlaceholderImage = require('@/assets/images/background-image.png'); export default function Index() { const [selectedImage, setSelectedImage] = useState<string | undefined>( undefined ); const [showAppOptions, setShowAppOptions] = useState<boolean>(false); const [isModalVisible, setIsModalVisible] = useState<boolean>(false); const [pickedEmoji, setPickedEmoji] = useState< ImageSourcePropType | undefined >(undefined); const [permissionResponse, requestPermission] = MediaLibrary.usePermissions(); const imageRef = useRef<View>(null); useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); const pickImageAsync = async () => { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsEditing: true, quality: 1, }); if (!result.canceled) { setSelectedImage(result.assets[0].uri); setShowAppOptions(true); } else { alert('You did not select any image.'); } }; const onReset = () => { setShowAppOptions(false); }; const onAddSticker = () => { setIsModalVisible(true); }; const onModalClose = () => { setIsModalVisible(false); }; const onSaveImageAsync = async () => { try { const localUri = await captureRef(imageRef, { height: 440, quality: 1, }); await MediaLibrary.saveToLibraryAsync(localUri); if (localUri) { alert('Saved!'); } } catch (e) { console.log(e); } }; return ( <GestureHandlerRootView style={styles.container}> <View style={styles.imageContainer}> <View ref={imageRef} collapsable={false}> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {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> ) : ( <View style={styles.footerContainer}> <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} /> <Button label="Use this photo" onPress={() => setShowAppOptions(true)} /> </View> )} <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}> <EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} /> </EmojiPicker> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#25292e', alignItems: 'center', }, imageContainer: { flex: 1, }, footerContainer: { flex: 1 / 3, alignItems: 'center', }, optionsContainer: { position: 'absolute', bottom: 80, }, optionsRow: { alignItems: 'center', flexDirection: 'row', }, });

现在,在应用中选择一张照片并添加一个贴纸。然后点击“保存”按钮。我们应该在 Android 和 iOS 上看到以下结果:

🌐 Now, choose a photo and add a sticker in the app. Then tap the "Save" button. We should see the following result on Android and iOS:

概括

🌐 Summary

Chapter 7: Take a screenshot

我们已成功使用 react-native-view-shotexpo-media-library 来截取屏幕并将其保存到设备的图库中。

在下一章中,让我们学习如何处理移动端和网页平台之间的差异,以在网页上实现相同的功能。

Next: 处理平台差异