在本教程中,了解如何使用第三方库和 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-handler
、react-native-reanimated
。我们可以根据用例在 React Native 目录 上找到数百个其他第三方库。
1
2
¥Prompt for permissions
需要敏感信息(例如访问设备的媒体库)的应用必须提示允许或拒绝访问的权限。使用来自 expo-media-library
的 usePermissions()
钩子,我们可以使用权限 status
和 requestPermission()
方法来请求访问权限。
¥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 status
and requestPermission()
method to ask for access.
当应用首次加载且权限状态既未授予也未拒绝时,status
的值为 null
。当请求权限时,用户可以授予权限或拒绝权限。我们可以添加一个条件来检查是否是 null
,如果是,则触发 requestPermission()
方法。获得访问权限后,status
的值将更改为 granted
。
¥When the app loads for the first time and the permission status is neither granted nor denied, the value of the status
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 null
, and if it is, trigger the requestPermission()
method. After getting the access, the value of the status
changes to granted
.
在 app/(tabs)/index.tsx 中添加以下代码片段:
¥Add the following code snippet inside the app/(tabs)/index.tsx:
import * as MediaLibrary from 'expo-media-library';
// ...rest of the code remains same
export default function Index() {
const [status, requestPermission] = MediaLibrary.usePermissions();
// ...rest of the code remains same
if (status === null) {
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.
从 react-native-view-shot
导入 captureRef
并从 React 导入 useRef
。
¥Import captureRef
from react-native-view-shot
and useRef
from React.
创建一个 imageRef
引用变量来存储捕获的屏幕截图图片的引用。
¥Create an imageRef
reference variable to store the reference of the screenshot image captured.
将 <ImageViewer>
和 <EmojiSticker>
组件封装在 <View>
中,然后将引用变量传递给它。
¥Wrap the <ImageViewer>
and <EmojiSticker>
components inside a <View>
and then pass the reference variable to it.
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
prop 设置为 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-shot
调用 captureRef()
方法来捕获视图的屏幕截图。它接受一个可选参数,我们可以在其中传递屏幕截图捕获区域的 width
和 height
。我们可以在 库的文档 中阅读有关可用选项的更多信息。
¥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()
方法还返回一个使用屏幕截图 URI 实现的 promise。我们将此 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:
import { View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { useState, useRef } from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import * as MediaLibrary from 'expo-media-library';
import { captureRef } from 'react-native-view-shot';
import { type ImageSource } from 'expo-image';
import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';
import IconButton from '@/components/IconButton';
import CircleButton from '@/components/CircleButton';
import EmojiPicker from '@/components/EmojiPicker';
import EmojiList from '@/components/EmojiList';
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<ImageSource | undefined>(undefined);
const [status, requestPermission] = MediaLibrary.usePermissions();
const imageRef = useRef<View>(null);
if (status === null) {
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
We've successfully used react-native-view-shot
and expo-media-library
to capture a screenshot and save it on the device's library.
In the next chapter, let's learn how to handle the differences between mobile and web platforms to implement the same functionality on web.