Skip to content

相机 / 相册调用

第七部分:常用原生功能开发

在 React Native 应用中,相机和相册调用是常见的功能,比如用户头像上传、图片分享等。本文将详细介绍如何在 React Native 应用中实现相机和相册调用功能。

1. 使用 Expo 相机

1.1 安装依赖

bash
# 使用 npm
npm install expo-camera expo-image-picker

# 使用 yarn
yarn add expo-camera expo-image-picker

1.2 请求权限

在使用相机和相册之前,需要请求相应的权限。

jsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';

export default function CameraExample() {
  const [image, setImage] = useState(null);

  useEffect(() => {
    (async () => {
      // 请求相册权限
      const { status: mediaLibraryStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
      // 请求相机权限
      const { status: cameraStatus } = await ImagePicker.requestCameraPermissionsAsync();
      
      if (mediaLibraryStatus !== 'granted' || cameraStatus !== 'granted') {
        Alert.alert('权限错误', '需要相机和相册权限才能使用此功能');
      }
    })();
  }, []);

  // 从相册选择图片
  const pickImage = async () => {
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [1, 1],
        quality: 0.8,
      });

      if (!result.canceled) {
        setImage(result.assets[0]);
      }
    } catch (error) {
      Alert.alert('错误', '选择图片失败');
    }
  };

  // 使用相机拍照
  const takePhoto = async () => {
    try {
      const result = await ImagePicker.launchCameraAsync({
        allowsEditing: true,
        aspect: [1, 1],
        quality: 0.8,
      });

      if (!result.canceled) {
        setImage(result.assets[0]);
      }
    } catch (error) {
      Alert.alert('错误', '拍照失败');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>相机和相册示例</Text>
      
      <TouchableOpacity style={styles.button} onPress={pickImage}>
        <Text style={styles.buttonText}>从相册选择</Text>
      </TouchableOpacity>
      
      <TouchableOpacity style={styles.button} onPress={takePhoto}>
        <Text style={styles.buttonText}>拍照</Text>
      </TouchableOpacity>
      
      {image && (
        <View style={styles.imageContainer}>
          <Image source={{ uri: image.uri }} style={styles.image} />
          <Text style={styles.imageInfo}>图片路径: {image.uri}</Text>
          <Text style={styles.imageInfo}>图片宽度: {image.width}</Text>
          <Text style={styles.imageInfo}>图片高度: {image.height}</Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    marginBottom: 15,
    width: '100%',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  imageContainer: {
    marginTop: 20,
    alignItems: 'center',
  },
  image: {
    width: 200,
    height: 200,
    borderRadius: 10,
    marginBottom: 10,
  },
  imageInfo: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5,
  },
});

2. 使用 react-native-image-picker

2.1 安装依赖

bash
# 使用 npm
npm install react-native-image-picker

# 使用 yarn
yarn add react-native-image-picker

2.2 配置权限

Android 配置

android/app/src/main/AndroidManifest.xml 文件中添加以下权限:

xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

iOS 配置

Info.plist 文件中添加以下权限:

xml
<key>NSCameraUsageDescription</key>
<string>需要使用相机来拍照</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册来选择图片</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册来保存图片</string>

2.3 示例代码

jsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image, Platform } from 'react-native';
import ImagePicker from 'react-native-image-picker';

export default function ImagePickerExample() {
  const [image, setImage] = useState(null);

  // 从相册选择图片
  const pickImage = () => {
    const options = {
      title: '选择图片',
      storageOptions: {
        skipBackup: true,
        path: 'images',
      },
    };

    ImagePicker.launchImageLibrary(options, (response) => {
      console.log('Response = ', response);

      if (response.didCancel) {
        console.log('用户取消了选择');
      } else if (response.error) {
        Alert.alert('错误', '选择图片失败');
      } else if (response.uri) {
        setImage(response);
      }
    });
  };

  // 使用相机拍照
  const takePhoto = () => {
    const options = {
      title: '拍照',
      storageOptions: {
        skipBackup: true,
        path: 'images',
      },
    };

    ImagePicker.launchCamera(options, (response) => {
      console.log('Response = ', response);

      if (response.didCancel) {
        console.log('用户取消了拍照');
      } else if (response.error) {
        Alert.alert('错误', '拍照失败');
      } else if (response.uri) {
        setImage(response);
      }
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>相机和相册示例</Text>
      
      <TouchableOpacity style={styles.button} onPress={pickImage}>
        <Text style={styles.buttonText}>从相册选择</Text>
      </TouchableOpacity>
      
      <TouchableOpacity style={styles.button} onPress={takePhoto}>
        <Text style={styles.buttonText}>拍照</Text>
      </TouchableOpacity>
      
      {image && (
        <View style={styles.imageContainer}>
          <Image source={{ uri: image.uri }} style={styles.image} />
          <Text style={styles.imageInfo}>图片路径: {image.uri}</Text>
          {image.width && <Text style={styles.imageInfo}>图片宽度: {image.width}</Text>}
          {image.height && <Text style={styles.imageInfo}>图片高度: {image.height}</Text>}
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    marginBottom: 15,
    width: '100%',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  imageContainer: {
    marginTop: 20,
    alignItems: 'center',
  },
  image: {
    width: 200,
    height: 200,
    borderRadius: 10,
    marginBottom: 10,
  },
  imageInfo: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5,
  },
});

3. 高级功能

3.1 图片上传

jsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image, ActivityIndicator } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import axios from 'axios';

export default function ImageUploadExample() {
  const [image, setImage] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [uploadStatus, setUploadStatus] = useState('');

  // 从相册选择图片
  const pickImage = async () => {
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [1, 1],
        quality: 0.8,
      });

      if (!result.canceled) {
        setImage(result.assets[0]);
        setUploadStatus('');
      }
    } catch (error) {
      Alert.alert('错误', '选择图片失败');
    }
  };

  // 上传图片
  const uploadImage = async () => {
    if (!image) {
      Alert.alert('错误', '请先选择图片');
      return;
    }

    setUploading(true);
    setUploadStatus('上传中...');

    try {
      const formData = new FormData();
      formData.append('image', {
        uri: image.uri,
        type: 'image/jpeg',
        name: 'photo.jpg',
      });

      const response = await axios.post('https://api.example.com/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });

      setUploadStatus('上传成功!');
      Alert.alert('成功', '图片上传成功!');
    } catch (error) {
      setUploadStatus('上传失败');
      Alert.alert('错误', '上传失败,请稍后重试');
    } finally {
      setUploading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>图片上传示例</Text>
      
      <TouchableOpacity style={styles.button} onPress={pickImage}>
        <Text style={styles.buttonText}>选择图片</Text>
      </TouchableOpacity>
      
      {image && (
        <View style={styles.imageContainer}>
          <Image source={{ uri: image.uri }} style={styles.image} />
          <TouchableOpacity 
            style={[styles.uploadButton, uploading && styles.buttonDisabled]}
            onPress={uploadImage}
            disabled={uploading}
          >
            {uploading ? (
              <ActivityIndicator color="#fff" />
            ) : (
              <Text style={styles.uploadButtonText}>上传图片</Text>
            )}
          </TouchableOpacity>
          {uploadStatus ? (
            <Text style={[styles.uploadStatus, uploadStatus === '上传成功!' && styles.successText]}>
              {uploadStatus}
            </Text>
          ) : null}
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    marginBottom: 15,
    width: '100%',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  imageContainer: {
    marginTop: 20,
    alignItems: 'center',
  },
  image: {
    width: 200,
    height: 200,
    borderRadius: 10,
    marginBottom: 15,
  },
  uploadButton: {
    backgroundColor: '#2196F3',
    padding: 15,
    borderRadius: 8,
    width: '100%',
    alignItems: 'center',
  },
  buttonDisabled: {
    backgroundColor: '#9e9e9e',
  },
  uploadButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  uploadStatus: {
    marginTop: 10,
    fontSize: 14,
    color: '#666',
  },
  successText: {
    color: '#4CAF50',
  },
});

3.2 多图片选择

jsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image, ScrollView } from 'react-native';
import * as ImagePicker from 'expo-image-picker';

export default function MultiImagePickerExample() {
  const [images, setImages] = useState([]);

  useEffect(() => {
    (async () => {
      const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
      if (status !== 'granted') {
        Alert.alert('权限错误', '需要相册权限才能使用此功能');
      }
    })();
  }, []);

  // 选择多张图片
  const pickMultipleImages = async () => {
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsMultipleSelection: true,
        quality: 0.8,
      });

      if (!result.canceled) {
        setImages(result.assets);
      }
    } catch (error) {
      Alert.alert('错误', '选择图片失败');
    }
  };

  // 清空图片
  const clearImages = () => {
    setImages([]);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>多图片选择示例</Text>
      
      <TouchableOpacity style={styles.button} onPress={pickMultipleImages}>
        <Text style={styles.buttonText}>选择多张图片</Text>
      </TouchableOpacity>
      
      {images.length > 0 && (
        <TouchableOpacity style={styles.clearButton} onPress={clearImages}>
          <Text style={styles.clearButtonText}>清空图片</Text>
        </TouchableOpacity>
      )}
      
      {images.length > 0 && (
        <ScrollView style={styles.imageList} horizontal showsHorizontalScrollIndicator={false}>
          {images.map((image, index) => (
            <View key={index} style={styles.imageContainer}>
              <Image source={{ uri: image.uri }} style={styles.image} />
              <Text style={styles.imageInfo}>图片 {index + 1}</Text>
            </View>
          ))}
        </ScrollView>
      )}
      
      {images.length === 0 && (
        <Text style={styles.noImagesText}>请选择图片</Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    textAlign: 'center',
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    marginBottom: 15,
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  clearButton: {
    backgroundColor: '#f44336',
    padding: 10,
    borderRadius: 8,
    marginBottom: 20,
    alignItems: 'center',
  },
  clearButtonText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '500',
  },
  imageList: {
    marginBottom: 20,
  },
  imageContainer: {
    marginRight: 10,
    alignItems: 'center',
  },
  image: {
    width: 150,
    height: 150,
    borderRadius: 10,
  },
  imageInfo: {
    marginTop: 5,
    fontSize: 14,
    color: '#666',
  },
  noImagesText: {
    textAlign: 'center',
    marginTop: 20,
    fontSize: 16,
    color: '#999',
  },
});

4. 常见问题与解决方案

问题 1:权限被拒绝

问题:用户拒绝了相机或相册权限。

解决方案

  • 在应用中添加权限请求逻辑
  • 当权限被拒绝时,引导用户手动开启权限
  • 提供替代方案,如从其他来源选择图片

问题 2:图片选择失败

问题:选择图片时出现错误。

解决方案

  • 检查设备权限设置
  • 检查应用权限配置
  • 处理异常情况,提供友好的错误提示

问题 3:图片上传失败

问题:上传图片时出现错误。

解决方案

  • 检查网络连接
  • 检查服务器端配置
  • 处理大图片上传的情况,考虑压缩图片
  • 提供上传进度和错误提示

问题 4:iOS 权限配置

问题:在 iOS 上无法访问相机或相册。

解决方案

  • Info.plist 文件中添加正确的权限描述
  • 确保权限描述清晰明了,说明为什么需要这些权限

问题 5:Android 权限配置

问题:在 Android 上无法访问相机或相册。

解决方案

  • AndroidManifest.xml 文件中添加正确的权限
  • 对于 Android 6.0+,需要动态请求权限
  • 检查设备的权限设置

5. 最佳实践

5.1 权限处理

  • 提前请求权限,避免在用户操作时才请求
  • 当权限被拒绝时,提供清晰的提示和引导
  • 处理权限请求的各种状态( granted, denied, restricted, undetermined )

5.2 图片处理

  • 根据需要设置合适的图片质量,平衡图片大小和清晰度
  • 对于大图片,考虑压缩处理
  • 处理图片选择取消的情况
  • 提供图片预览功能

5.3 错误处理

  • 捕获并处理所有可能的错误
  • 提供友好的错误提示
  • 记录错误信息,便于调试

5.4 用户体验

  • 添加加载状态,如上传时显示进度
  • 提供清晰的操作反馈
  • 优化图片选择和上传的流程
  • 考虑网络状况,提供离线支持

6. 总结

本文介绍了在 React Native 应用中实现相机和相册调用的方法,包括使用 Expo 相机和 react-native-image-picker 库。通过本文的学习,你应该掌握了以下内容:

  1. 如何请求相机和相册权限
  2. 如何从相册选择图片
  3. 如何使用相机拍照
  4. 如何上传图片
  5. 如何处理多图片选择
  6. 常见问题的解决方案
  7. 最佳实践

在实际开发中,合理使用这些技术,可以创建出更加丰富、用户友好的图片处理功能,提升应用的整体体验。

© 2026 编程马·菜鸟教程 版权所有