截图

编辑页面

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


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

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

到目前为止,我们已经使用了第三方库,例如 react-native-gesture-handlerreact-native-reanimated。根据不同的使用场景,我们可以在 React Native Directory 中找到数百个其他第三方库。
观看:在你的通用 Expo 应用中截取屏幕截图
观看:在你的通用 Expo 应用中截取屏幕截图

使用 react-native-view-shot 捕获屏幕截图,并使用 expo-media-library 将其保存到设备的媒体库中。


1

安装库

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

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

2

请求权限

需要访问敏感信息的应用,例如访问设备媒体库,必须提示用户授予或拒绝访问权限。使用 expo-image-picker 中的 useMediaLibraryPermissions() hook,我们可以使用 permissionResponserequestPermission() 方法来请求访问权限。这个 hook 会同时请求读取和写入权限,这既涵盖了从媒体库中选择图片,也涵盖了将截图保存到其中。

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

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

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

3

创建一个 ref 来保存当前视图

我们将使用 react-native-view-shot 允许用户在应用内截取屏幕截图。这个库会使用 captureRef() 方法将 <View> 的截图捕获为图像。它会返回所捕获截图图像文件的 URI。

  1. react-native-view-shot 导入 captureRef,并从 React 导入 useRef
  2. 创建一个 imageRef 引用变量来保存所捕获截图的引用。
  3. <ImageViewer><EmojiSticker> 组件包裹在一个 <View> 中,然后把引用变量传给它。
app/(tabs)/index.tsx
import { 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> 组件只对背景图片和表情贴纸进行截图。

4

捕获截图并保存

我们可以在 onSaveImageAsync() 函数内部调用 react-native-view-shotcaptureRef() 方法来捕获视图截图。它接受一个可选参数,我们可以传入截图区域的 widthheight。关于可用选项的更多信息,请参阅该库的文档

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

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

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] = ImagePicker.useMediaLibraryPermissions(); 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('你没有选择任何图片。'); } }; 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('已保存!'); } } 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="重置" onPress={onReset} /> <CircleButton onPress={onAddSticker} /> <IconButton icon="save-alt" label="保存" onPress={onSaveImageAsync} /> </View> </View> ) : ( <View style={styles.footerContainer}> <Button theme="primary" label="选择一张照片" onPress={pickImageAsync} /> <Button label="使用这张照片" 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 上看到以下结果:

总结

Chapter 7: Take a screenshot

我们已经成功使用 react-native-view-shot expo-media-library 捕获了屏幕截图,并将其保存到设备的媒体库中。

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

Next: 处理平台差异