使用react-native-image-picker选择图片并可进行裁剪功能

关于react-native-image-picker的使用详解,网上讲的有很多,这里就不用再说别的了。我们今天主要说的是使用自定义弹框和选择图片时的裁剪功能。(裁剪需要用到原生的方法)
第一:关于自定义弹框
我们都知道react-native-image-picker有封装好的弹出框信息,可自己进行配置。
但是往往有些UI设计,可能需要显示成在原生中通用的弹出框样式,这个时候我们就需要自己对弹出框信息进行配置。关于Model的具体用法可以参考官网# Modal进行了解
代码如下:

<Modal
    animationType={"fade"}
    transparent={true}
    visible={this.state.modalVisible}
    onRequestClose={()=>{this.dismiss()}}
>
    <TouchableOpacity style={ styles.mask} onPress={()=>this.dismiss}>
             <View style={{width:690*Rate,height:230*Rate,borderRadius:16*Rate,backgroundColor:'white',marginLeft:30*Rate}}>
                    <TouchableHighlight style={styles.photoButton} underlayColor='#f0f0f0' onPress={()=>this.chooseFromLiabary()}>
                                <Text style={styles.buttonText}>从相册选择</Text>
                   </TouchableHighlight>
                   <View style={{backgroundColor: '#6D6D72',height: 2 * Rate}}></View>
                         <TouchableHighlight style={styles.photographButton} underlayColor='#f0f0f0' onPress={()=>this.directToRecord()}>
                                <Text style={styles.buttonText}>拍照上传</Text>
                         </TouchableHighlight>
                  </View>
                  <View style={{height: 16 * Rate}}></View>
                        <TouchableHighlight style={styles.button} underlayColor='#f0f0f0' onPress={this.dismiss.bind(this)}>
                            <Text style={styles.buttonText}>取消</Text>
                        </TouchableHighlight>
                 <View style={{height: 30 * Rate}}></View>
    </TouchableOpacity>
</Modal>

按照上面的方法,显示的样式如下。(这个弹框的样式可以按照需求自由进行修改,没有什么特殊的要求。)

IMG_0280.PNG

至于在Xcode里面的配置,我们这里就不啰嗦了。
第二:选择图片时的裁剪功能
接下来我们要说的就是裁剪功能了,这个时候我们需要使用Xcode打开项目,创建新文件:RNRootManager.h和RNRootManager.m。裁剪使用到了原生的第三方库# RSKImageCropper

RNRootManager.h的代码如下:

#import <Foundation/Foundation.h>
#import <React/RCTRootView.h>
@protocol RNRootManagerDelegate <NSObject>
@end
@interface RNRootManager : NSObject<RCTBridgeModule>
@property (nonatomic,weak) id<RNRootManagerDelegate> delegate;
@end

RNRootManager.m的代码如下:

#import "RNRootManager.h"
#import "RSKImageCropViewController.h"
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#define SCREEN_WIDTH     [[UIScreen mainScreen] bounds].size.width
#define SCREEN_HEIGHT    [[UIScreen mainScreen] bounds].size.height
 static RCTResponseSenderBlock _callback;

@interface RNRootManager() <RSKImageCropViewControllerDelegate,RSKImageCropViewControllerDataSource>
@property(nonatomic,assign)CGFloat mutiple ;
@end

@implementation RNRootManager

RCT_EXPORT_MODULE(ReactToNative);

static id<RNRootManagerDelegate> m_delegate;//不知为何类的属性在rn方法里为空,故吾设一静态变量存之,以缓燃眉之急,待得佳计改之
- (void)setDelegate:(id<RNRootManagerDelegate>)delegate {
  
  _delegate = delegate;
  if (m_delegate) {
    m_delegate = nil;
  }
  m_delegate = delegate;
}
RCT_EXPORT_METHOD(cropImageWithDic:(NSDictionary *)dic callback:(RCTResponseSenderBlock)callback) {//图片剪切
  if (_callback) {
    _callback = NULL;
  }
  _callback = callback;
  dispatch_async(dispatch_get_main_queue(), ^{
    NSString *uri=[dic objectForKey:@"uri"];//图片资源
    NSString *height=[dic objectForKey:@"height"];//宽度
    NSString *width=[dic objectForKey:@"width"];//长度
    CGFloat mutiple = [height floatValue]/[width floatValue];
    self.mutiple = mutiple;
    UIImage *portraitImg = NULL;
    __weak typeof(self) weakSelf = self;
    
    if([uri rangeOfString:@"file"].length>0){ //相机照片
      NSString *url = [uri substringFromIndex:7];
      portraitImg = [[UIImage alloc]initWithContentsOfFile:url];
      RSKImageCropViewController *imageCropVC = [[RSKImageCropViewController alloc] initWithImage:portraitImg cropMode:RSKImageCropModeCustom];
      NSLog(@"original %f %f",portraitImg.size.width,portraitImg.size.height);
      imageCropVC.delegate = weakSelf;
      imageCropVC.dataSource = weakSelf;
      UIViewController *vc = [self getCurrentVC];
      UIViewController *ddd = [[UIViewController alloc]init];
      ddd.view.backgroundColor = [UIColor redColor];
      [vc presentViewController:imageCropVC animated:NO completion:nil];
    }
    else{
      ALAssetsLibrary *lib = [[ALAssetsLibrary alloc] init];
      [lib assetForURL:[NSURL URLWithString:uri] resultBlock:^(ALAsset *asset) {
        
        ALAssetRepresentation *assetRep = [asset defaultRepresentation];
        CGImageRef imgRef = [assetRep fullResolutionImage];
        UIImage *img = [UIImage imageWithCGImage:imgRef
                                           scale:assetRep.scale
                                     orientation:(UIImageOrientation)assetRep.orientation];
        RSKImageCropViewController *imageCropVC = [[RSKImageCropViewController alloc] initWithImage:img cropMode:RSKImageCropModeCustom];
        imageCropVC.delegate =weakSelf;
        imageCropVC.dataSource = weakSelf;
        UIViewController *vc =[self getCurrentVC];
        [vc presentViewController:imageCropVC animated:NO completion:nil];
      } failureBlock:^(NSError *error) {
      }];
    }
  });
}
//取消
- (void)imageCropViewControllerDidCancelCrop:(RSKImageCropViewController *)controller {
  [controller dismissViewControllerAnimated:NO completion:nil];
}
//裁剪
- (void)imageCropViewController:(RSKImageCropViewController *)controller
                   didCropImage:(UIImage *)croppedImage
                  usingCropRect:(CGRect)cropRect
                  rotationAngle:(CGFloat)rotationAngle {
  [controller dismissViewControllerAnimated:YES completion:^{
    NSData *data = nil;
    if(UIImagePNGRepresentation(croppedImage) == nil){
      data = UIImagePNGRepresentation(croppedImage);
    }
    else{
      data = UIImageJPEGRepresentation(croppedImage, 0.5);
    }
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docDir = [paths objectAtIndex:0];
    NSString *saveUrl = [NSString stringWithFormat:@"%@/%fcropImg.jpg",docDir,[[NSDate date] timeIntervalSince1970]];
    BOOL isSave =  [data writeToFile:saveUrl atomically:YES];
    if(isSave){
      _callback(@[[NSNull null],@{@"uri":[NSString stringWithFormat:@"file://%@",saveUrl]}]);
    }
    else{
      _callback(@[@"裁剪图片失败",@{@"uri":@""}]);
    }
  }];
}
- (CGRect)imageCropViewControllerCustomMaskRect:(RSKImageCropViewController *)controller {
  CGFloat Pading = 10;
  CGFloat mutiple = self.mutiple;
  CGFloat w = SCREEN_WIDTH-Pading;
  CGFloat h = w *mutiple;
  if (h>SCREEN_HEIGHT) {
    h = SCREEN_HEIGHT -Pading;
    w = h/mutiple;
  }
  
  CGFloat x =(SCREEN_WIDTH -w)*0.5;
  CGFloat y =(SCREEN_HEIGHT -h)/2;
  return CGRectMake(x,y,w,h);
}

- (UIBezierPath *)imageCropViewControllerCustomMaskPath:(RSKImageCropViewController *)controller {
  CGFloat Pading = 10;
  CGFloat mutiple = self.mutiple;
  CGFloat w = SCREEN_WIDTH-Pading;
  CGFloat h = w *mutiple;
  if (h>SCREEN_HEIGHT) {
    h = SCREEN_HEIGHT -Pading;
    w = h/mutiple;
  }
  CGFloat x =(SCREEN_WIDTH -w)*0.5;
  CGFloat y =(SCREEN_HEIGHT -h)/2;
  UIBezierPath *path=[UIBezierPath bezierPathWithRoundedRect:CGRectMake(x,y,w,h) cornerRadius:0];
  return path;
}
@end

接下来就是接入到RN代码里面:
第一步引入NativeModules;

import {
    NativeModules,
rom 'react-native';
var ReactToNative = NativeModules.ReactToNative;

第二步定义的原生方法:

//图片剪切方法
export const cropImageWithDic=(dic,callback)=>{ //
    ReactToNative.cropImageWithDic(dic,(error,event)=>{
        callback(error,event)
    })
}

准备好这些之后就可以进行下面的步骤了。
为了方便我们把关于图片剪裁的内容单独放在一个文件里面,文件名为:UIImagePicker.js。然后在需要使用图片剪裁的地方倒入这个文件。
引入ImagePicker的代码如下:

import ImagePicker from './UIImagePicker';
<ImagePicker
                    ref="img"
                    {...this.props}
                    onChoose={(medias) => {
                        this.getImgMedias(medias)
                    }}
                    cropHeight={this.state.cropHeight}
                    cropWidth={this.state.cropWidth}
                />
... ...
... ...
... ...
getImgMedias(medias) 
        console.log('medias----->',medias);
        let filename = new Date().getTime();
        let file = {
            uri: medias[0].fileId ? medias[0].uri : medias[0].uri.uri,
            type: 'multipart/form-data',
            name: filename + '.jpg',
            fileId: medias[0].fileId ? medias[0].fileId : null
        };
        console.log('file',file);
        //调服务器存储还是要做什么操作,自己看着办

    }

UIImagePicker.js的部分代码如下:
//点击拍照上传的代码比较简单,this.props.cropHeight和this.props.cropWidth表示要裁剪图片的宽高比

    directToRecord() {
        this.dismiss();   //隐藏弹出框
        let options  = imageOptions;
        let tempOption = new Object();
        tempOption.mediaType=options.mediaType;
        tempOption.quality=options.quality;
        tempOption.allowsEditing =options.allowsEditing;
        tempOption.noData = options.noData;
        //
        RNImagePicker.launchCamera(tempOption,(response) => {
            if (response.uri) {
                cropImageWithDic({uri:response.uri.toString(),height:this.props.cropHeight.toString(),width:this.props.cropWidth.toString()},(error,event)=>{
                    this.chooseToResponse([{uri:{
                        uri:event.uri
                    }}]);
                })
            }
            else if(response.error){
                Alert.alert('提示',response.error);
            }
        })
    }

chooseToResponse(photos) {
        if (this.props.onChoose && photos.length !== 0){
            this.props.onChoose(photos);
        }
    }

点击相册裁剪图片的代码稍微复杂一点,首先需要拿到相册的所有图片,然后按照想要显示的样式进行显示(需要另写一个class用来显示图片),选择一张图片之后再进行裁剪,这里需要注意的是# CameraRoll的使用,需要在Xcode里面进行相应的配置,否则相册里面的照片是拿不到的(如果不是同事的指点,大概还不可能这么快发现这个问题)代码如下:

    //从相册选择图片
    chooseFromLiabary() {
        this.dismiss();
        const{navigator} = this.props;
        navigator.push({
            name: 'CustomView',
            component:CustomView,
            passProps:{
                isUAlbum:false,
                count:1,
                cropHeight:this.props.cropHeight,
                cropWidth:this.props.cropWidth,
                onChoose:(response)=>{
                    this.chooseToResponse(response);
                }
            }
        })
    }
export class CustomView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            dataSource: new ListView.DataSource({
                rowHasChanged: (row1, row2) => row1 !== row2,
            }),
            selectedPhotos:[],
            selectedCount:0,
            count:0,
            photos:[],//手机本地图片
            currentPage:0,
            isLoadMore:false,
            localPhotos:[],
            showLoading:false
        }
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.state.photos !== nextState.photos) {
            return true;
        }
        return false;
    }
    onPress(rowData,callback,isShow,data) {
        //返回裁剪图片
        const {onChoose} = this.props;
        this.props.navigator.popN(1);
        onChoose([{uri:{
            uri:data.uri
        }}])
    }
    toDismiss(){
        const {navigator} = this.props;
        navigator.pop();
    }
    componentDidMount() {
        this.setState({count:this.props.count,photos:[],currentPage:0})
        const {isUAlbum,albumId} = this.props;
        if (isUAlbum) {//u8 相册
            if(Platform.OS === "ios") {
                InteractionManager.runAfterInteractions(()=>{
                    ReadPhoto(albumId,(res)=>{
                        let list  = [];
                        res.map((record)=>{
                            if(record){
                                let dict = {};
                                if(record.type == 0){
                                    dict.uri = record.imageUrl;
                                    dict.zoomUri = record.zoomImageUrl;
                                    dict.fileId = record.fileId;
                                    dict.isHttp = true;
                                    list.push(dict);
                                }
                            }
                        })
                        if(list.length !==0){
                            this.setState({localPhotos:list});
                        }
                    },(error)=>{});
                })
            }
        } else {
            let that =this;
            let fetchParams = {
                first: 9999,
                assetType: 'Photos'
            };
         CameraRoll.getPhotos(fetchParams).then((data)=>{
                let edges = data.edges;
                let photos = [];
                for (let i in edges) {
                    let dict = {};
                    dict.uri = edges[i].node.image.uri;
                    dict.isHttp = false;
                    dict.height = edges[i].node.image.height;
                    dict.width = edges[i].node.image.width;
                    photos.push(dict);
                }
                that.setState({photos:photos,isLoadMore:false});
            }).catch(()=>{
                Alert.alert('提示',"获取相册照片失败",[
                    {text:'确定',onPress:()=>{}},
                ]);
            });
        }
    }

    renderRow(rowData){
        return(
            <CustomCell uri={rowData} onPress={this.onPress.bind(this,rowData)} isUAlbum={this.props.isUAlbum}
                        cropHeight={this.props.cropHeight}
                        cropWidth={this.props.cropWidth}
                        navigator={this.props.navigator}/>
        )
    }

    render() {
        return(
            <View style={{width:width,height:height,backgroundColor:'#EBEDF0',}}>
                <ListView
                    onEndReachedThreshold={40}
                    showsVerticalScrollIndicator={false}
                    enableEmptySections={true}
                    initialListSize = {60}
                    dataSource={this.state.dataSource.cloneWithRows(this.state.photos.length!==0?this.state.photos:this.state.localPhotos)}
                    renderRow={this.renderRow.bind(this)}
                    contentContainerStyle={{flexDirection:'row',flexWrap:'wrap',paddingTop:Rate*10,paddingBottom:Rate*30}}
                />
            </View>
        )
    }
}

class CustomCell extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isShow: false,
        }
    }
    render() {
        return (
            <TouchableWithoutFeedback onPress={this.onPress.bind(this)} >
                <ImageBackground resizeMode='cover' source={this.props.isUAlbum?{uri:this.props.uri.zoomUri}:{uri:this.props.uri.uri}}
                       style={{flexDirection:'row-reverse',marginRight:Rate*5,marginTop:Rate*5,width:(width-Rate*25)/4,height:(width-Rate*25)/4}}>
                    <View style={{justifyContent:'space-between',padding:Rate*5}}>
                        <Image source={require('../../../app/resource/audioRecordImg/selected.png')} style={{height:Rate*40,width:Rate*40,opacity:this.state.isShow?1:0}}/>
                    </View>
                </ImageBackground>
            </TouchableWithoutFeedback>
        );
    }
    onPress() {
        cropImageWithDic({uri:this.props.isUAlbum?this.props.uri.zoomUri.toString():this.props.uri.uri.toString(),height:'1',width:'1'},(error,event)=>{
            this.props.onPress();
        })
    }
}

以上基本就是全部的代码逻辑了,个人写的比较的繁琐,有可能哪些地方写的不合适,欢迎评论给我建议。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,416评论 25 707
  • 手抄佛经的想法来源于石头说。石头君写到:喧嚣的世间,忙碌和浮躁是大多数人的写照。由此滋生的杂念,时刻扰乱着我...
    瑶瑶_9bbd阅读 423评论 0 1
  • 每次认识一个异性男生刚刚开始觉得聊天有共同话题,开始总是喋喋不休的聊天,很投机。最后却觉得彼此之间聊天越来越没有话...
    宇宇同学阅读 336评论 0 0
  • 喜欢是什么、爱又是什么? 我认识他已经差不多有八年了、从我读初二开始我知道了他、他个子有一米七五、身材不魁梧、严格...
    小乌龟爬阅读 243评论 0 0