react-native-vision-camera核心功能解析:拍照录像全攻略
引言:重新定义React Native相机体验
你还在为React Native项目中的相机功能卡顿、画质差、API复杂而烦恼吗?作为开发者,我们深知在移动应用中实现专业级相机功能的痛点——系统相机API碎片化、性能损耗严重、自定义功能受限。react-native-vision-camera作为一款高性能相机库,通过深度整合原生相机框架与React Native架构,提供了接近原生的拍摄体验。本文将从实战角度全面解析其拍照录像核心功能,带你掌握从基础配置到高级优化的全流程解决方案。
读完本文你将获得:
- 拍照录像功能的完整实现方案
- 设备适配与性能优化的实战技巧
- 错误处理与边缘情况的解决方案
- 10+可直接复用的代码片段
- 基于真实设备测试的参数配置指南
核心功能架构概览
react-native-vision-camera采用模块化设计,核心功能围绕Camera组件展开,通过分层抽象实现对原生相机能力的高效调用。其架构可分为以下层次:
核心API调用流程如下:
环境配置与基础集成
前置依赖与权限配置
react-native-vision-camera需要React Native 0.60+环境,且必须配置相机和麦克风权限:
Android配置(android/app/src/main/AndroidManifest.xml):
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
iOS配置(ios/项目名/Info.plist):
<key>NSCameraUsageDescription</key>
<string>需要访问相机拍摄照片和视频</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风录制视频声音</string>
安装与项目集成
通过npm或yarn安装:
npm install react-native-vision-camera
# 或
yarn add react-native-vision-camera
iOS需额外安装pod依赖:
cd ios && pod install && cd ..
拍照功能全解析
基础拍照流程实现
拍照功能通过takePhoto方法实现,基本流程包含设备选择、权限检查和拍照调用三个步骤:
import React, { useRef, useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
export function PhotoCaptureExample() {
const camera = useRef<Camera>(null);
const [photo, setPhoto] = useState(null);
const permission = useCameraPermission();
const device = useCameraDevice('back');
// 检查权限
if (permission.status !== 'granted') {
return <View><Text>请授予相机权限</Text></View>;
}
if (!device) {
return <View><Text>未找到相机设备</Text></View>;
}
const takePhoto = async () => {
if (!camera.current) return;
try {
const photoFile = await camera.current.takePhoto({
flash: 'auto',
enableAutoRedEyeReduction: true,
});
setPhoto(photoFile);
console.log('照片保存路径:', photoFile.path);
} catch (e) {
console.error('拍照失败:', e);
}
};
return (
<View style={styles.container}>
<Camera
ref={camera}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
photo={true}
/>
<View style={styles.buttonContainer}>
<Button title="拍照" onPress={takePhoto} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
position: 'relative',
},
buttonContainer: {
position: 'absolute',
bottom: 20,
alignSelf: 'center',
},
});
拍照参数深度配置
takePhoto方法支持丰富的参数配置,以满足不同场景需求:
| 参数名 | 类型 | 说明 | 平台支持 |
|---|---|---|---|
| flash | 'on' | 'off' | 'auto' | 闪光灯模式 | 双平台 |
| enableAutoRedEyeReduction | boolean | 自动红眼消除 | iOS |
| enableAutoDistortionCorrection | boolean | 自动畸变校正 | iOS |
| enableShutterSound | boolean | 是否播放快门声 | 双平台 |
| path | string | 自定义保存路径 | 双平台 |
高级配置示例:
// 专业模式拍照配置
const photoOptions = {
flash: 'auto', // 自动闪光灯
enableAutoRedEyeReduction: true, // 启用红眼消除
enableAutoDistortionCorrection: true, // 启用畸变校正
path: '/storage/emulated/0/DCIM/VisionCamera', // 自定义保存目录
};
// 无声拍照(仅Android)
const silentPhotoOptions = {
enableShutterSound: false,
};
照片输出格式与元数据
拍照成功后返回PhotoFile对象,包含丰富的图像信息:
interface PhotoFile {
path: string; // 文件路径
width: number; // 宽度(像素)
height: number; // 高度(像素)
isRawPhoto: boolean; // 是否为RAW格式
orientation: Orientation; // 方向信息
metadata?: { // 详细元数据
Orientation: number; // EXIF方向
'{Exif}': {
DateTimeOriginal: string; // 拍摄时间
ExposureTime: number; // 曝光时间
FNumber: number; // 光圈值
ISOSpeedRatings: number[];// ISO值
FocalLength: number; // 焦距
};
// 更多元数据...
};
}
元数据应用示例:
// 从照片元数据中提取拍摄信息
const extractPhotoInfo = (photo: PhotoFile) => {
if (!photo.metadata?.['{Exif}']) return null;
const exif = photo.metadata['{Exif}'];
return {
timestamp: exif.DateTimeOriginal,
exposure: exif.ExposureTime,
aperture: exif.FNumber,
iso: exif.ISOSpeedRatings[0],
focalLength: exif.FocalLength,
};
};
录像功能完全指南
视频录制基础流程
视频录制通过startRecording、stopRecording等方法实现,需要处理录制状态管理和错误回调:
import React, { useRef, useState } from 'react';
import { View, StyleSheet, Button, Text } from 'react-native';
import { Camera, useCameraDevice, useCameraPermission, useMicrophonePermission } from 'react-native-vision-camera';
export function VideoRecordingExample() {
const camera = useRef<Camera>(null);
const [isRecording, setIsRecording] = useState(false);
const [videoDuration, setVideoDuration] = useState(0);
const cameraPermission = useCameraPermission();
const micPermission = useMicrophonePermission();
const device = useCameraDevice('back');
// 检查权限
if (cameraPermission.status !== 'granted' || micPermission.status !== 'granted') {
return <View><Text>请授予相机和麦克风权限</Text></View>;
}
if (!device) return <View><Text>未找到相机设备</Text></View>;
let recordingTimer;
const startRecording = async () => {
if (!camera.current) return;
setIsRecording(true);
setVideoDuration(0);
// 启动计时器
recordingTimer = setInterval(() => {
setVideoDuration(prev => prev + 1);
}, 1000);
try {
await camera.current.startRecording({
flash: 'off',
fileType: 'mp4',
onRecordingError: (error) => {
console.error('录制错误:', error);
setIsRecording(false);
clearInterval(recordingTimer);
},
onRecordingFinished: (video) => {
console.log('视频保存路径:', video.path);
console.log('视频时长:', video.duration, '秒');
setIsRecording(false);
clearInterval(recordingTimer);
setVideoDuration(0);
},
});
} catch (e) {
console.error('启动录制失败:', e);
setIsRecording(false);
}
};
const stopRecording = async () => {
if (!camera.current || !isRecording) return;
try {
await camera.current.stopRecording();
} catch (e) {
console.error('停止录制失败:', e);
}
};
return (
<View style={styles.container}>
<Camera
ref={camera}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
video={true}
audio={true} // 需要麦克风权限
/>
<View style={styles.infoBar}>
<Text style={styles.duration}>{videoDuration}s</Text>
</View>
<View style={styles.controls}>
{isRecording ? (
<Button title="停止录制" onPress={stopRecording} color="red" />
) : (
<Button title="开始录制" onPress={startRecording} />
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
position: 'relative',
},
infoBar: {
position: 'absolute',
top: 20,
left: 20,
backgroundColor: 'rgba(0,0,0,0.5)',
padding: 8,
borderRadius: 4,
},
duration: {
color: 'white',
fontWeight: 'bold',
},
controls: {
position: 'absolute',
bottom: 20,
alignSelf: 'center',
},
});
高级录制功能实现
视频暂停与恢复
支持暂停录制功能,实现分段录制场景:
// 视频暂停/恢复功能
const [isPaused, setIsPaused] = useState(false);
const togglePauseRecording = async () => {
if (!camera.current) return;
try {
if (isPaused) {
await camera.current.resumeRecording();
setIsPaused(false);
// 恢复计时器
} else {
await camera.current.pauseRecording();
setIsPaused(true);
// 暂停计时器
}
} catch (e) {
console.error('暂停/恢复失败:', e);
}
};
自定义视频编码与质量
通过配置videoCodec和比特率参数控制视频质量:
// 高质量视频录制配置
const highQualityRecordingOptions = {
flash: 'off',
fileType: 'mp4',
videoCodec: 'h265', // 使用HEVC编码,更高压缩率
// 比特率控制(两种方式):
// 1. 直接指定比特率(单位:bps)
videoBitRate: 10_000_000, // 10Mbps
// 2. 使用预设等级
// videoBitRate: 'high', // 'low' | 'normal' | 'high' | 'extra-high'
onRecordingError: (error) => {/* 错误处理 */},
onRecordingFinished: (video) => {/* 完成处理 */},
};
视频防抖与HDR配置
通过相机格式选择实现视频防抖和HDR功能:
import { useCameraFormat } from 'react-native-vision-camera';
// 选择支持4K和防抖的相机格式
const format = useCameraFormat(device, [
{ videoResolution: 'ultra-high' }, // 优先4K分辨率
{ videoStabilizationMode: 'cinematic' }, // 优先电影级防抖
{ fps: 60 }, // 优先60fps
]);
// 组件中应用
<Camera
ref={camera}
style={StyleSheet.absoluteFill}
device={device}
format={format}
isActive={true}
video={true}
audio={true}
videoStabilizationMode="cinematic"
videoHdr={true} // 启用视频HDR
/>
视频文件结构与属性
录制完成后返回VideoFile对象:
interface VideoFile {
path: string; // 文件路径
duration: number; // 时长(秒)
width: number; // 宽度(像素)
height: number; // 高度(像素)
size: number; // 文件大小(字节)
}
视频信息提取示例:
// 获取视频文件大小和分辨率信息
const getVideoInfo = async (video: VideoFile) => {
const fileSizeMB = (video.size / (1024 * 1024)).toFixed(2);
const resolution = `${video.width}x${video.height}`;
const duration = new Date(video.duration * 1000).toISOString().substr(11, 8);
return {
fileSize: `${fileSizeMB} MB`,
resolution,
duration,
};
};
高级功能与性能优化
相机设备管理与切换
实现前后摄像头切换和多设备管理:
const [cameraPosition, setCameraPosition] = useState<'front' | 'back'>('back');
const device = useCameraDevice(cameraPosition);
// 切换摄像头
const toggleCamera = () => {
setCameraPosition(prev => prev === 'back' ? 'front' : 'back');
};
// UI中添加切换按钮
<Button title="切换摄像头" onPress={toggleCamera} />
多摄像头设备检测:
import { Camera } from 'react-native-vision-camera';
// 获取所有可用相机设备
const getAllDevices = () => {
const devices = Camera.getAvailableCameraDevices();
return {
backCameras: devices.filter(d => d.position === 'back'),
frontCameras: devices.filter(d => d.position === 'front'),
hasDualCamera: devices.some(d => d.name?.includes('ultra-wide')),
hasTelephoto: devices.some(d => d.name?.includes('telephoto')),
};
};
手势控制实现(缩放与对焦)
实现 pinch-to-zoom 和点击对焦功能:
import Animated, { useAnimatedGestureHandler, useSharedValue } from 'react-native-reanimated';
const ReanimatedCamera = Animated.createAnimatedComponent(Camera);
export function CameraWithGestures() {
const camera = useRef<Camera>(null);
const zoom = useSharedValue(1);
const device = useCameraDevice('back');
// 缩放手势处理
const onPinchGesture = useAnimatedGestureHandler({
onStart: (_, context) => {
context.startZoom = zoom.value;
},
onActive: (event, context) => {
const scale = event.scale;
const startZoom = context.startZoom ?? 1;
// 计算新的缩放值,限制在设备支持范围内
const newZoom = Math.min(
Math.max(startZoom * scale, device?.minZoom ?? 1),
device?.maxZoom ?? 10
);
zoom.value = newZoom;
},
});
// 点击对焦处理
const onFocusTap = (event: GestureResponderEvent) => {
if (!camera.current || !device?.supportsFocus) return;
// 获取点击位置(相对于相机视图)
const { locationX, locationY } = event.nativeEvent;
camera.current.focus({ x: locationX, y: locationY });
// 显示对焦指示器动画
setFocusPoint({ x: locationX, y: locationY });
setTimeout(() => setFocusPoint(null), 1000);
};
// 应用动画属性
const animatedProps = useAnimatedProps(() => ({
zoom: zoom.value,
}));
return (
<PinchGestureHandler onGestureEvent={onPinchGesture}>
<Animated.View style={StyleSheet.absoluteFill} onTouchEnd={onFocusTap}>
<ReanimatedCamera
ref={camera}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
animatedProps={animatedProps}
/>
{/* 对焦指示器 */}
{focusPoint && (
<View style={[styles.focusIndicator, { left: focusPoint.x - 25, top: focusPoint.y - 25 }]} />
)}
</Animated.View>
</PinchGestureHandler>
);
}
const styles = StyleSheet.create({
focusIndicator: {
width: 50,
height: 50,
borderWidth: 2,
borderColor: 'white',
borderRadius: 50,
position: 'absolute',
},
});
错误处理与异常恢复
react-native-vision-camera定义了丰富的错误类型,需要针对性处理:
import { CameraCaptureError, CameraRuntimeError } from 'react-native-vision-camera';
// 错误处理工具函数
const handleCameraError = (error: unknown) => {
if (error instanceof CameraRuntimeError) {
switch (error.code) {
case 'permission/camera-permission-denied':
return { type: 'permission', message: '相机权限被拒绝,请在设置中启用' };
case 'device/no-device':
return { type: 'device', message: '未检测到相机设备' };
case 'session/camera-not-ready':
return { type: 'session', message: '相机准备中,请稍后再试' };
// 更多错误类型...
}
} else if (error instanceof CameraCaptureError) {
switch (error.code) {
case 'capture/insufficient-storage':
return { type: 'storage', message: '存储空间不足,无法保存媒体' };
case 'capture/file-io-error':
return { type: 'file', message: '文件写入失败' };
case 'capture/recording-in-progress':
return { type: 'recording', message: '已有录制进程在进行中' };
// 更多错误类型...
}
}
return { type: 'unknown', message: '发生未知错误' };
};
// 组件中应用
const takePhotoWithErrorHandling = async () => {
try {
const photo = await camera.current.takePhoto({/* 选项 */});
// 处理成功
} catch (e) {
const errorInfo = handleCameraError(e);
showErrorDialog(errorInfo.message);
// 针对可恢复错误进行处理
if (errorInfo.type === 'session') {
// 尝试重启相机
setIsActive(false);
setTimeout(() => setIsActive(true), 1000);
}
}
};
性能优化最佳实践
相机格式选择策略
使用useCameraFormat钩子选择最优相机格式:
// 不同场景的格式选择策略
const getFormatStrategy = (mode: 'photo' | 'video' | 'slow-mo') => {
switch (mode) {
case 'photo':
return [
{ photoResolution: 'max' }, // 最高照片分辨率
{ photoAspectRatio: 1 }, // 优先1:1比例(正方形)
{ supportsPhotoHdr: true }, // 启用HDR
];
case 'video':
return [
{ videoResolution: 'high' }, // 1080p分辨率
{ fps: 60 }, // 60fps
{ videoStabilizationMode: 'standard' }, // 标准防抖
];
case 'slow-mo':
return [
{ fps: 120 }, // 120fps以上支持慢动作
{ videoResolution: 'medium' }, // 降低分辨率以支持高帧率
];
}
};
// 使用策略选择格式
const format = useCameraFormat(device, getFormatStrategy(currentMode));
帧率控制与电池优化
合理设置帧率平衡性能与功耗:
// 根据场景动态调整帧率
const getFpsRange = (isLowPowerMode: boolean, isRecording: boolean) => {
if (isLowPowerMode) {
return [15, 30]; // 低电量模式下降低帧率
} else if (isRecording) {
return 60; // 录制时使用高帧率
} else {
return [30, 60]; // 预览时动态帧率
}
};
// 应用到相机组件
<Camera
{/* ...其他属性 */}
fps={getFpsRange(isLowPowerMode, isRecording)}
enableBufferCompression={!isRecording} // 非录制时启用缓冲区压缩
/>
实战案例:功能完整的相机应用
综合示例:相机应用核心组件
以下是整合拍照、录像、切换摄像头、手势控制等功能的完整相机组件:
import React, { useRef, useState, useEffect } from 'react';
import { View, StyleSheet, TouchableOpacity, Text, Image } from 'react-native';
import {
Camera,
useCameraDevice,
useCameraPermission,
useMicrophonePermission,
useCameraFormat,
} from 'react-native-vision-camera';
import { PinchGestureHandler } from 'react-native-gesture-handler';
import Animated, { useAnimatedGestureHandler, useSharedValue, useAnimatedProps } from 'react-native-reanimated';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
// 定义相机模式
type CameraMode = 'photo' | 'video';
const ReanimatedCamera = Animated.createAnimatedComponent(Camera);
export function AdvancedCamera() {
// 状态管理
const [mode, setMode] = useState<CameraMode>('photo');
const [isRecording, setIsRecording] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [recordingTime, setRecordingTime] = useState(0);
const [cameraPosition, setCameraPosition] = useState<'front' | 'back'>('back');
const [flashMode, setFlashMode] = useState<'off' | 'on' | 'auto'>('auto');
const [focusPoint, setFocusPoint] = useState<{ x: number; y: number } | null>(null);
// 引用与动画值
const camera = useRef<Camera>(null);
const zoom = useSharedValue(1);
const recordingTimer = useRef<NodeJS.Timeout | null>(null);
// 权限检查
const cameraPermission = useCameraPermission();
const micPermission = useMicrophonePermission();
// 设备与格式
const device = useCameraDevice(cameraPosition);
const format = useCameraFormat(device, [
{ videoAspectRatio: 16 / 9 },
mode === 'photo' ? { photoResolution: 'max' } : { videoResolution: 'high' },
{ fps: mode === 'video' ? 60 : 30 },
]);
// 处理权限状态
if (cameraPermission.status !== 'granted' || (mode === 'video' && micPermission.status !== 'granted')) {
return (
<View style={styles.permissionContainer}>
<Text style={styles.permissionText}>
{cameraPermission.status !== 'granted'
? '需要相机权限'
: '需要麦克风权限'}
</Text>
</View>
);
}
if (!device || !format) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>未找到相机设备</Text>
</View>
);
}
// 缩放手势处理
const onPinchGesture = useAnimatedGestureHandler({
onStart: (_, context) => {
context.startZoom = zoom.value;
},
onActive: (event, context) => {
const startZoom = context.startZoom ?? 1;
const newZoom = startZoom * event.scale;
zoom.value = Math.min(Math.max(newZoom, device.minZoom), device.maxZoom);
},
});
// 对焦处理
const handleFocus = (event: React.TouchEvent) => {
if (!camera.current || !device.supportsFocus) return;
const { locationX, locationY } = event.nativeEvent;
camera.current.focus({ x: locationX, y: locationY });
setFocusPoint({ x: locationX, y: locationY });
setTimeout(() => setFocusPoint(null), 1500);
};
// 切换相机
const toggleCamera = () => {
setCameraPosition(prev => (prev === 'back' ? 'front' : 'back'));
setZoomValue(1); // 重置缩放
};
// 切换闪光灯
const toggleFlash = () => {
const modes: Array<'off' | 'on' | 'auto'> = ['off', 'on', 'auto'];
const currentIndex = modes.indexOf(flashMode);
setFlashMode(modes[(currentIndex + 1) % modes.length]);
};
// 切换拍摄模式
const toggleMode = () => {
setMode(prev => (prev === 'photo' ? 'video' : 'photo'));
setFlashMode('auto'); // 重置闪光灯
};
// 开始录制
const startRecording = async () => {
if (!camera.current || isRecording) return;
setIsRecording(true);
setIsPaused(false);
setRecordingTime(0);
// 启动计时器
recordingTimer.current = setInterval(() => {
setRecordingTime(prev => prev + 1);
}, 1000);
try {
await camera.current.startRecording({
flash: flashMode === 'auto' ? 'off' : flashMode,
fileType: 'mp4',
videoBitRate: 'high',
onRecordingError: (error) => {
console.error('录制错误:', error);
stopRecording();
},
onRecordingFinished: (video) => {
console.log('视频保存成功:', video.path);
// 导航到预览页面
// navigation.navigate('Preview', { path: video.path, type: 'video' });
},
});
} catch (e) {
console.error('启动录制失败:', e);
stopRecording();
}
};
// 停止录制
const stopRecording = async () => {
if (!camera.current || !isRecording || isPaused) return;
try {
await camera.current.stopRecording();
} catch (e) {
console.error('停止录制失败:', e);
} finally {
setIsRecording(false);
if (recordingTimer.current) {
clearInterval(recordingTimer.current);
recordingTimer.current = null;
}
}
};
// 暂停/恢复录制
const togglePauseRecording = async () => {
if (!camera.current || !isRecording) return;
try {
if (isPaused) {
await camera.current.resumeRecording();
// 恢复计时器
recordingTimer.current = setInterval(() => {
setRecordingTime(prev => prev + 1);
}, 1000);
} else {
await camera.current.pauseRecording();
// 暂停计时器
if (recordingTimer.current) {
clearInterval(recordingTimer.current);
recordingTimer.current = null;
}
}
setIsPaused(prev => !prev);
} catch (e) {
console.error('暂停/恢复失败:', e);
}
};
// 拍照
const takePhoto = async () => {
if (!camera.current || isRecording) return;
try {
const photo = await camera.current.takePhoto({
flash: flashMode,
enableAutoRedEyeReduction: true,
});
console.log('照片保存成功:', photo.path);
// 导航到预览页面
// navigation.navigate('Preview', { path: photo.path, type: 'photo' });
} catch (e) {
console.error('拍照失败:', e);
}
};
// 格式化录制时间
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
// 动画属性
const animatedProps = useAnimatedProps(() => ({
zoom: zoom.value,
}));
const setZoomValue = (value: number) => {
zoom.value = Math.min(Math.max(value, device.minZoom), device.maxZoom);
};
return (
<View style={styles.container}>
<PinchGestureHandler onGestureEvent={onPinchGesture}>
<View style={StyleSheet.absoluteFill} onTouchEnd={handleFocus}>
<ReanimatedCamera
ref={camera}
style={StyleSheet.absoluteFill}
device={device}
format={format}
isActive={true}
photo={mode === 'photo'}
video={mode === 'video'}
audio={mode === 'video'}
flash={flashMode}
fps={mode === 'video' ? 60 : 30}
animatedProps={animatedProps}
enableFpsGraph={__DEV__} // 开发模式显示FPS图表
/>
{/* 对焦指示器 */}
{focusPoint && (
<View style={[styles.focusIndicator, { left: focusPoint.x - 25, top: focusPoint.y - 25 }]} />
)}
{/* 顶部控制栏 */}
<View style={styles.topBar}>
<TouchableOpacity style={styles.controlButton} onPress={toggleFlash}>
<MaterialIcons
name={
flashMode === 'on' ? 'flash-on' :
flashMode === 'off' ? 'flash-off' : 'flash-auto'
}
color="white"
size={24}
/>
</TouchableOpacity>
<TouchableOpacity style={styles.controlButton} onPress={toggleCamera}>
<MaterialIcons name="flip-camera-ios" color="white" size={24} />
</TouchableOpacity>
</View>
{/* 底部控制栏 */}
<View style={styles.bottomBar}>
<TouchableOpacity style={styles.modeButton} onPress={toggleMode}>
<Text style={[styles.modeText, { fontWeight: mode === 'photo' ? 'bold' : 'normal' }]}>
照片
</Text>
<Text style={[styles.modeText, { fontWeight: mode === 'video' ? 'bold' : 'normal' }]}>
视频
</Text>
</TouchableOpacity>
{/* 拍摄按钮 */}
<TouchableOpacity
style={[styles.captureButton, isRecording && styles.recordingButton]}
onPress={mode === 'photo' ? takePhoto : isRecording ? stopRecording : startRecording}
onLongPress={mode === 'video' && !isRecording ? startRecording : undefined}
activeOpacity={0.8}
>
{isRecording && isPaused && (
<View style={styles.pauseIndicator} />
)}
</TouchableOpacity>
{/* 预览按钮 */}
<View style={styles.previewButton}>
{/* 这里应该显示最后拍摄的照片缩略图 */}
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



