You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:在React Native中用Expo Image Picker实现圆形区域手动裁剪保存照片

React Native 圆形区域拖动裁剪照片实现方案

给你一套基于Expo的实现方案,刚好匹配你要的「圆形区域内拖动照片裁剪」需求:

先装必备依赖

打开终端执行命令,安装需要的Expo库:

expo install expo-image-picker expo-image-manipulator expo-file-system expo-gesture-handler expo-media-library

Expo项目无需额外配置,裸React Native项目需要按照react-native-gesture-handler官方文档做基础配置。

完整代码实现

import { useState } from 'react';
import { Button, View, Image, StyleSheet, Alert } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as ImageManipulator from 'expo-image-manipulator';
import * as MediaLibrary from 'expo-media-library';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

export default function CircleCropScreen() {
  const [imageUri, setImageUri] = useState(null);
  const [offset, setOffset] = useState({ x: 0, y: 0 });
  const CIRCLE_SIZE = 300; // 圆形裁剪区域的尺寸,可按需调整

  // 调用相册选图
  const pickImage = async () => {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: false, // 关闭Expo默认编辑,用自定义裁剪逻辑
      quality: 1,
    });

    if (!result.canceled) {
      setImageUri(result.assets[0].uri);
      setOffset({ x: 0, y: 0 }); // 选新图后重置偏移位置
    }
  };

  // 处理照片拖动手势
  const panGesture = Gesture.Pan()
    .onUpdate((event) => {
      // 这里可以加边界判断,限制照片不能移出圆形区域过多,示例暂时简化
      setOffset({
        x: event.translationX,
        y: event.translationY,
      });
    });

  // 裁剪并保存照片
  const handleSaveCrop = async () => {
    if (!imageUri) return;

    try {
      // 获取原始图片尺寸
      const { width: originalWidth, height: originalHeight } = await ImageManipulator.getImageInfoAsync(imageUri);
      
      // 计算裁剪比例:因为照片用了cover模式,需要把拖动偏移量转换成原始图片坐标
      const scale = Math.max(CIRCLE_SIZE / originalWidth, CIRCLE_SIZE / originalHeight);
      const cropSize = CIRCLE_SIZE / scale;
      
      const cropX = (originalWidth - cropSize) / 2 - (offset.x / scale);
      const cropY = (originalHeight - cropSize) / 2 - (offset.y / scale);

      // 执行裁剪操作
      const manipResult = await ImageManipulator.manipulateAsync(
        imageUri,
        [
          {
            crop: {
              originX: Math.max(0, cropX), // 避免裁剪坐标为负
              originY: Math.max(0, cropY),
              width: Math.min(cropSize, originalWidth), // 避免超出图片范围
              height: Math.min(cropSize, originalHeight),
            },
          },
          { resize: { width: CIRCLE_SIZE, height: CIRCLE_SIZE } }, // 缩放到圆形尺寸
        ],
        { format: ImageManipulator.SaveFormat.PNG }
      );

      // 保存到系统相册
      await MediaLibrary.saveToLibraryAsync(manipResult.uri);
      Alert.alert('成功', '裁剪后的照片已保存到相册');
    } catch (err) {
      Alert.alert('出错', '裁剪保存失败,请重试');
      console.error(err);
    }
  };

  return (
    <View style={styles.container}>
      <Button title="选择照片" onPress={pickImage} />
      
      {imageUri && (
        <View style={styles.cropWrap}>
          {/* 圆形遮罩:通过overflow:hidden只显示圆形内的照片 */}
          <View style={[styles.circleMask, { width: CIRCLE_SIZE, height: CIRCLE_SIZE }]}>
            <GestureDetector gesture={panGesture}>
              <Image
                source={{ uri: imageUri }}
                style={[styles.cropImage, { transform: [{ translateX: offset.x }, { translateY: offset.y }] }]}
                resizeMode="cover"
              />
            </GestureDetector>
          </View>
          
          <Button title="保存裁剪结果" onPress={handleSaveCrop} style={styles.saveBtn} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  cropWrap: {
    marginTop: 20,
    alignItems: 'center',
  },
  circleMask: {
    borderRadius: CIRCLE_SIZE / 2,
    overflow: 'hidden',
    borderWidth: 3,
    borderColor: '#ffffff',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.3,
    shadowRadius: 4,
  },
  cropImage: {
    width: '100%',
    height: '100%',
  },
  saveBtn: {
    marginTop: 20,
  },
});

关键逻辑说明

  1. 圆形遮罩:通过borderRadius设置为尺寸一半,配合overflow:hidden,只显示圆形范围内的照片
  2. 拖动控制:用react-native-gesture-handler的Pan手势实现照片的流畅拖动,如需限制拖动边界,可在onUpdate中添加坐标判断
  3. 裁剪计算:把拖动的偏移量按图片缩放比例转换为原始图片的裁剪坐标,确保裁剪区域和视觉上的圆形区域完全匹配
  4. 保存功能:用expo-media-library将裁剪后的图片保存到系统相册,需要在app.json中配置权限:
{
  "expo": {
    "plugins": [
      [
        "expo-media-library",
        {
          "photosPermission": "允许应用访问相册以保存裁剪后的照片"
        }
      ]
    ]
  }
}

内容的提问来源于stack exchange,提问作者berkayer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 15:50:50