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构建,所以有一些细节没有罗列的特别清楚。例如图片保存在服务器的哪里,怎么让静态资源不拦截,等等信息,但是大致套路就是这样。流程掌握清楚,还是蛮简单的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。