React-Native图片多选上传,及头像处理等可动态增加删除2018-11-08

今天公司业务用到图片的多选上传或者删除,目前只看到了相关的图片上传组件。
例如: https://github.com/ivpusic/react-native-image-crop-picker
还是非常强大的,具体搭建就看官方demo足矣。接下来我演示,如何做图片的动态添加或者删除,以及上传到我们的server服务,然后再用来显示在我们的前端。

版本:
react-native 0.57
后台Server是java服务,其中controller采用的SpringMvc框架。

基本的界面布局

//注意,这里我引用了NativeBase组件,你可以直接用View代替即可。
 <Container>
            <Content style={{backgroundColor:"#ffffff"}}>
                <View>
                    <View style={{flexDirection: "row",justifyContent:"space-between"}}>
                        //以下文本框,可以忽略
                        <Text style={{marginLeft:10,marginTop:20}}>备注</Text>
                        <Text style={{marginLeft:10,marginTop:20,flexDirection:"row-reverse"}}>最多输入100字</Text>
                    </View>
                    <Textarea style={{height:150,marginLeft:10,marginRight:10,borderRadius:5}} bordered placeholder="请输备注"  onChangeText={
                        (text) => {
                            this.setState({ description:text });
                        }
                    }/>
                </View>
                <View style={{flexDirection:"row",flexWrap:"wrap"} }>
                    {
                       //重点,这个方法负责添加我们的图片。
                        this._renderAddImageView()
                    }
                </View>
                <Spinner
                    visible={this.state.spinner}
                    textContent={'加载中'}
                    textStyle={styles.spinnerTextStyle}
                />
            </Content>
        </Container>

_renderAddImageView方法

   _renderAddImageView(){
    //判断state中是否存在图片路径信息,如果没有,就显示添加图片的按钮。
    console.info(this.state.avatarSource.size>0)
    //pages 变量,用来存储,我们遍历出来的路径,生成的ImageBackground显示节点。
    var pages =[];
    if (this.state.avatarSource.size>0) {
        let images = this.state.avatarSource;
        images.forEach(url => {
            pages.push(
                     <ImageBackground
                         index = {1}
                         source={require('../image/service/xuxian.png')}
                         style ={styles.image}>
                         <ImageBackground source={{uri:url}} style={styles.uploadImage} />
                         <TouchableOpacity style={styles.rightDelButton} onPress = {()=>this.deleteLoadedImage(url)}>
                             <Image style={{width:20, height:20}} onPress = {()=>alert(23)} source={require('../image/service/shanchu.png')}></Image>
                         </TouchableOpacity>
                     </ImageBackground>
                )
        })
        //注意这里,如果图片数量小于5,那么我们需要显示可以继续添加。
        if(this.state.avatarSource.size<5){
            pages.push(
            <ImageBackground
                source={require('../image/service/xuxian.png')}
                style ={styles.image}>
                <TouchableOpacity onPress = {this.addOnClicked.bind(this)}>
                    <Image style={{width:60, height:60}} source={require('../image/service/tianjia.png')}></Image>
                </TouchableOpacity>
                <Text style ={styles.normalTitle}>上传图片</Text>
                //这里显示最多可以上传多少张
                <Text style ={styles.normalText}>(最多能上传5张)</Text>
            </ImageBackground>
            )
        }
        return (pages)
    }

接下来看具体的添加过程,实际上就是 addOnClicked 方法。

    addOnClicked(){
    //这里对应,native-image-crop-picker组件,看一下就知道为什么了。
    ImagePicker.openPicker({
        multiple: true,
        minFiles:3,
        maxFiles:5,
        cropperChooseText:"确定",
        cropperCancelText:"取消",
    }).then(images => {
        //这里我就采用for循环遍历上传了,因为我的是多选,返回的是一个Images的数组。如果是单选的话,这里直接返回的就是当前图片的信息。
        for(let image in images){
   //HTTPUtil.baseUrL 是对应你上传的方法的url,dispatch/uploadImage就是具体的方法地址。   this.uploadFile(HTTPUtil.baseUrL+'dispatch/uploadImage',images[image].path,images[image].path,"image.jpg");
        }
    });
}

uploadFile方法

    async uploadFile(url, fileUrl,fileName) {
    //这里要注意,把当前this,存储下来。
    let thisObj = this;
    //可以忽略,就是过度效果。
    thisObj.setState({ spinner: true });
    let formData = new FormData();
    formData.append('file', {
        uri: fileUrl,
        name: fileName,
        type: 'image/jpeg'
    });
    const fetchOptions = {
        method: 'POST',
        body: formData
    };

    fetch(url,fetchOptions).
    then(function(response) {
        thisObj.setState({ spinner: false });
        return response.json();
    }).then(function(data) {
         //这里的url 是你上传完,server给返回的url地址,理论上就是一个get请求,就可以拿到图片信息。
        let url = HTTPUtil.baseUrL+"upload/"+data.data;
        let imageUrls= new Set();;
        imageUrls = thisObj.state.avatarSource;
        //把当前的图片url,存起来
        imageUrls.add(url);
        //这里调用setState,来更新我们的视图层。
        thisObj.setState({avatarSource:imageUrls})
        thisObj.setState({ spinner: false });
    }).catch(function(e) {
        console.info(e);
    });
}

点击删除图片

上文中,存在deleteLoadedImage方法在_renderAddImageView中,接下来我们看实现。

//删除加载的图片
deleteLoadedImage(url){
    let imageUrls= new Set();;
    imageUrls = this.state.avatarSource;
    //从set中删除掉url
    imageUrls.delete(url);
    //重新刷新视图
    this.setState({avatarSource:imageUrls})
}

项目中,用到的样式和png资源。

删除按钮


image.png

添加按钮


image.png

虚线边框
image.png

样式代码

    /**
 * 获取屏幕宽高
 */
const deviceHeight = Dimensions.get("window").height;
const deviceWidth = Dimensions.get("window").width;
    
 const styles = {
normalTitle:{
    textAlign:"center"
},
normalText:{
    textAlign:"center"
},
image:{
    alignItems:"center",
    justifyContent:"center",
    width:deviceWidth/2-20,
    height:deviceWidth/2-20,
    marginLeft:10,
    marginTop:10,
},
uploadImage:{
    alignItems:"center",
    justifyContent:"center",
    width:deviceWidth/2-30,
    height:deviceWidth/2-30,
},
rightDelButton:{
    position: 'absolute',
    top: -5,
    left:Platform.OS==="ios"?18:deviceWidth/2-30,
    margin: -1,
    flexDirection:"row-reverse",
}
}

Server端代码

   @ResponseBody
@RequestMapping(value = "/uploadImage", method = RequestMethod.POST)
public JsonResult uploadImage(MultipartFile file) {
    String newFileName = "";
    try {
        //获取文件原始名称
        String originalFilename = file.getOriginalFilename();
        InputStream fis = file.getInputStream();
        //上传图片
        if (file != null && originalFilename != null && originalFilename.length() > 0) {
            //新的图片名称
            newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
            //向输出流中写入数据
            FileOutputStream fos = new FileOutputStream(uploadPath + newFileName);
            //先定义一个字节缓冲区,减少I/O次数,提高读写效率
            byte[] buffer = new byte[10240];
            int size = 0;
            while ((size = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, size);
            }
            fis.close();
            fos.close();
            //将内存中的数据写入磁盘

        }
    } catch (Exception e) {
        logger.error(e.toString());
    }
    return new JsonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), newFileName);
}

返回路径给前端。

界面布局截图

image.png

gif演示效果

a0df8466-fbe4-4a8e-bc75-720e09391b77.gif

总结

1.其实这个图片上传,也适用于平时应用的一些头像上传,只不过根据组件api变成单选即可。
2.图片上传时,最好给过渡效果,因为很多网络情况不好情况下,是蛮耗时的操作。我的应用中采用了react-native-loading-spinner-overlay组件,有兴趣也可以百度看一下。大体就是basepost之前开始刷新,直到异步,返回url关闭刷新操作。确保上传动作可以正确执行完毕。
3.返回的路径,假如需要保存,那么需要把返回回来的url再存到关系型数据库,例如mysql中,这样下次加载信息,直接从url访问,就达到了我们读取上次保存的图片信息。
我的QQ337241905,如果对图片上传,或者是server端有疑问我都可以解答。因为我的项目是用spring boot构建,所以有一些细节没有罗列的特别清楚。例如图片保存在服务器的哪里,怎么让静态资源不拦截,等等信息,但是大致套路就是这样。流程掌握清楚,还是蛮简单的。

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