关于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>
按照上面的方法,显示的样式如下。(这个弹框的样式可以按照需求自由进行修改,没有什么特殊的要求。)
至于在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();
})
}
}
以上基本就是全部的代码逻辑了,个人写的比较的繁琐,有可能哪些地方写的不合适,欢迎评论给我建议。