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