拍摄屏幕截图
编辑
在本教程中,学习如何使用第三方库和 Expo 媒体库来捕获屏幕截图。
在本章中,我们将学习如何使用第三方库拍摄屏幕截图并将其保存在设备的媒体库中。我们将使用 react-native-view-shot 来拍摄屏幕截图,并使用 expo-media-library 将图像保存在设备的媒体库中。
到目前为止,我们已经使用了第三方库,如react-native-gesture-handler,react-native-reanimated。我们可以根据用例在 React Native Directory 找到数百个其他第三方库。

1
2
请求权限
需要敏感信息的应用程序,如访问设备的媒体库,必须提示用户授权以允许或拒绝访问。使用来自 expo-media-library 的 usePermissions() 钩子,我们可以使用权限 permissionResponse 和 requestPermission() 方法请求访问权限。
当应用第一次加载且权限状态既未授予也未拒绝时,permissionResponse 的值为 null。当请求权限时,用户可以选择授予或拒绝权限。我们可以添加一个条件来检查是否未授予。如果未授予,则触发 requestPermission() 方法。在获得访问权限后,permissionResponse 的值更改为 granted。
在 app/(tabs)/index.tsx 中添加以下代码片段:
import { useEffect, useState } from 'react'; import * as MediaLibrary from 'expo-media-library'; // ...其余代码保持不变 export default function Index() { const [permissionResponse, requestPermission] = MediaLibrary.usePermissions(); // ...其余代码保持不变 useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); // ...其余代码保持不变 }
3
创建引用以保存当前视图
我们将使用 react-native-view-shot 允许用户在应用中拍摄屏幕截图。此库使用 captureRef() 方法捕获 <View> 的屏幕截图作为图像。它返回捕获的屏幕截图图像文件的 URI。
- 从
react-native-view-shot导入captureRef和从 React 导入useRef。 - 创建一个
imageRef引用变量来存储所捕获屏幕截图的引用。 - 将
<ImageViewer>和<EmojiSticker>组件包裹在一个<View>中,然后将引用变量传递给它。
import { useEffect, useState, useRef } from 'react'; import { captureRef } from 'react-native-view-shot'; export default function Index() { const imageRef = useRef<View>(null); // ...其余代码保持不变 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> {/* ...其余代码保持不变 */} </GestureHandlerRootView> ); }
在上面的代码片段中,collapsable 属性设置为 false。这允许 <View> 组件仅捕获背景图像和表情贴纸的屏幕截图。
4
捕获屏幕截图并保存
我们可以通过在 onSaveImageAsync() 函数中调用来自 react-native-view-shot 的 captureRef() 方法来捕获视图的屏幕截图。它接受一个可选参数,我们可以在其中传递捕获区域的 width 和 height。我们可以在 库的文档 中阅读更多可用选项。
captureRef() 方法还返回一个承诺,承诺以屏幕截图的 URI 兑现。我们将这个 URI 作为参数传递给 MediaLibrary.saveToLibraryAsync(),并将屏幕截图保存到设备的媒体库中。
在 app/(tabs)/index.tsx 中,用以下代码更新 onSaveImageAsync() 函数:
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 上看到以下结果:
摘要
第七章:截取屏幕截图
我们成功使用 react-native-view-shot 和 expo-media-library 捕获了 屏幕截图并将其保存到设备的库中。
在下一章中,让我们学习如何处理移动平台和网页之间的差异,以在网页上实现相同功能。