图片上传参考文档:https://docs.expo.io/versions/latest/sdk/imagepicker.html Promise文档参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise ~~~ <TouchableOpacity onPress={() => { let picPromise = new Promise((resolve, reject) => { resolve(Expo.ImagePicker.launchImageLibraryAsync({ mediaTypes:'Images', allowsEditing:true, aspect:[200,200], quality:1, base64:false, exif:false })); }); picPromise.then(function(successMessage){ console.log(JSON.stringify(successMessage)); console.log(new Date().toString()); }); }}> <View style={[styles.findView,styles.findMT]}> <Image source={require('./icon/ic_games_black_24dp.png')} style={[styles.findViewIcon,{tintColor:'red'}]} /> <Text style={styles.findViewText}>上传图片</Text> </View> </TouchableOpacity> ~~~ 代码说明 ~~~ Expo.ImagePicker.launchCameraAsync(options) ~~~ allowsEditing 是否允许编辑图片 ![](https://box.kancloud.cn/aba4aecfd429ed43b66e53869edb0931_543x874.png) aspect 截屏大小 数组格式 quality 透明度 0 - 1 base64 是否返回base64 数据 exif 图像是否包含exif 数据 ![](https://box.kancloud.cn/fa3d108c991cf019c366ed0b581b84b7_807x195.jpg) 返回格式 ~~~ { "cancelled":false, "height":1611, "width":2148, "uri":"file:///data/user/0/host.exp.exponent/cache/cropped1814158652.jpg" } ~~~ #### 编写图片上传代码 ~~~ var formData = new FormData(); //自定义字段设置 formData.append("username", "Groucho"); formData.append("id", 123456); //图片选项设置 formData.append('pic',{ type : 'image/jpeg',//图片类型 uri : successMessage.uri,//图片本地地址 name : "gggggg",//上传图片的文件名,一般为图片名字 }); var request = new XMLHttpRequest(); request.open("POST", apiUrl + '/pic.php'); request.send(formData); //上传进度显示 if (request.upload){ request.upload.onprogress = (event)=>{ if (event.lengthComputable){ let perent = (event.loaded / event.total).toFixed(2)*100; // 输出上传进度 ToastAndroid.show('已经上传了 '+perent+'%',ToastAndroid.SHORT); // if(parent == 100){ // ToastAndroid.show('图片上传成功',ToastAndroid.SHORT); // } } } } request.onload = ()=>{ if(request.status == 200){ ToastAndroid.show('图片上传成功',ToastAndroid.SHORT); //打印返回的json数据 console.log(JSON.stringify(request.response)); } } ~~~ 服务器接收代码(php) ~~~ <?php if (isset($_FILES['pic'])){ $pic = $_FILES['pic']; if ($pic['error'] === 0){ $name = md5($pic['name'].time()).'.jpg'; @move_uploaded_file($pic['tmp_name'], __DIR__.'/'.$name); } } header("content-type:text/json;charset=utf-8"); echo json_encode(array('msg'=>'成功')); ~~~ #### 完整代码 ~~~ import React from 'react'; import { ToastAndroid,FlatList, StyleSheet,View, Text,Button,Image,StatusBar,WebView,TouchableOpacity,TextInput,AsyncStorage} from 'react-native'; import { TabNavigator,StackNavigator} from 'react-navigation'; const apiUrl = 'http://192.168.10.119/app3'; const userAgent = 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Mobile Safari/537.36'; //动态组件 class DynamicScreen extends React.Component { static navigationOptions = { title: '动态', tabBarIcon: ({ focused, tintColor }) => ( <Image source={require('./icon/ic_home_black_24dp.png')} style={{ width: 28, height: 28, tintColor: tintColor }} /> ), }; constructor(props) { super(props); this.state = { page: 1, refreshing: false, data: {}, totalPage:10 }; this.requestData(); }; handleRefresh = () => { this.setState({ page: 1, refreshing: true, data: [], }, () => { this.requestData(); }); } handleRefresh2 = () => { this.setState({ page: (this.state.page+1), refreshing: true }, () => { this.requestData(); }); } requestData = () => { if(this.state.page > this.state.totalPage){ ToastAndroid.show('没有数据啦 !', ToastAndroid.SHORT); this.setState({refreshing: false}); return false; } const url = apiUrl + "/data.php?page="+this.state.page; fetch(url).then(res => { return res.json(); }).then(res => { this.setState({ data: [...this.state.data, ...res], refreshing: false, totalPage:2 }); }).catch(err => { this.setState({refreshing: false}); ToastAndroid.show('获取数据失败',ToastAndroid.SHORT); }); } render() { return ( <View style={styles.container}> <FlatList keyExtractor={item => item.id} data={this.state.data} refreshing={this.state.refreshing} onRefresh={this.handleRefresh} onEndReached={this.handleRefresh2} onEndReachedThreshold={0.5} renderItem={({item}) => ( <TouchableOpacity onPress={() => this.props.navigation.navigate('dynamicDetail',{url:(apiUrl + '/read.html'),title:item.title})}> <View style={styles.item}> <View style={{flex:1}}> <Text style={styles.title}>{item.title}</Text> <Text style={styles.info}>{item.info}</Text> <Text style={styles.time}>{item.time}</Text> </View> </View> </TouchableOpacity> )} ItemSeparatorComponent={ ()=>( <View style={{backgroundColor:'#EBEBEB',height:5}}> </View> ) } ListEmptyComponent={ ()=>( <View style={{height:50,flex:1,alignItems:'center',justifyContent:'center',backgroundColor:'#fff'}}> <Text style={{color:'red'}}>暂无数据</Text> </View> ) } /> </View> ); } } //动态组件详情组件---webview组件 class DynamicDetailScreen extends React.Component { constructor(props) { super(props); }; static navigationOptions = ({ navigation }) => ({ title: navigation.state.params.title, headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#fff' }); render(){ return ( <View style={styles.container}> <WebView source={{uri:this.props.navigation.state.params.url}} userAgent={userAgent} /> </View> ); } } //发现组件 class FindScreen extends React.Component { static navigationOptions = { title: '发现', tabBarIcon: ({ focused, tintColor }) => ( <Image source={require('./icon/ic_group_work_black_24dp.png')} style={{ width: 28, height: 28, tintColor: tintColor }} /> ), }; render() { return ( <View style={[styles.container,styles.findBody]}> <View style={styles.findView}> <Image source={require('./icon/ic_group_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}> 朋友圈 </Text> </View> <View style={styles.findView}> <Image source={require('./icon/ic_filter_center_focus_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>扫一扫</Text> </View> <View style={styles.findView}> <Image source={require('./icon/ic_search_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>搜一搜</Text> </View> <View style={styles.findView}> <Image source={require('./icon/ic_shopping_cart_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>购物</Text> </View> <View style={[styles.findView,styles.findMT]}> <Image source={require('./icon/ic_games_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>游戏</Text> </View> <View style={styles.findView}> <Image source={require('./icon/ic_code_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>小程序</Text> </View> </View> ); } } //我的组件 class MyScreen extends React.Component { constructor(props){ super(props); } static navigationOptions = { title: '我的', tabBarIcon: ({ focused, tintColor }) => ( <Image source={require('./icon/ic_account_circle_black_24dp.png')} style={{ width: 28, height: 28, tintColor: tintColor }} /> ), }; render() { return ( <View style={styles.container}> <TouchableOpacity onPress={() => this.props.navigation.navigate('login')}> <View style={{height:160,alignItems:'center',justifyContent:'center',backgroundColor:'#374760',marginBottom:10}}> <Image source={require('./icon/my.png')} style={{ width: 96, height: 96,tintColor:'#F0F0F0' }} /> <Text style={{color:'#F0F0F0',marginTop:10,fontSize:16}}>立即登录</Text> </View> </TouchableOpacity> <TouchableOpacity onPress={() => { let picPromise = new Promise((resolve, reject) => { resolve(Expo.ImagePicker.launchImageLibraryAsync({ mediaTypes:'Images', allowsEditing:true, aspect:[1,1], quality:1, base64:false, exif:false })); }); picPromise.then(function(successMessage){ if(!successMessage.cancelled){ //开始上传图片 var formData = new FormData(); //自定义字段设置 formData.append("username", "Groucho"); formData.append("id", 123456); //图片选项设置 formData.append('pic',{ type : 'image/jpeg',//图片类型 uri : successMessage.uri,//图片本地地址 name : "gggggg",//上传图片的文件名,一般为图片名字 }); var request = new XMLHttpRequest(); request.open("POST", apiUrl + '/pic.php'); request.send(formData); //上传进度显示 if (request.upload){ request.upload.onprogress = (event)=>{ if (event.lengthComputable){ let perent = (event.loaded / event.total).toFixed(2)*100; // 输出上传进度 ToastAndroid.show('已经上传了 '+perent+'%',ToastAndroid.SHORT); // if(parent == 100){ // ToastAndroid.show('图片上传成功',ToastAndroid.SHORT); // } } } } request.onload = ()=>{ if(request.status == 200){ ToastAndroid.show('图片上传成功',ToastAndroid.SHORT); //打印返回的json数据 console.log(JSON.stringify(request.response)); } } } else { ToastAndroid.show('您取消上传图片',ToastAndroid.SHORT); } }); }}> <View style={[styles.findView,styles.findMT]}> <Image source={require('./icon/ic_games_black_24dp.png')} style={[styles.findViewIcon,{tintColor:'red'}]} /> <Text style={styles.findViewText}>上传图片</Text> </View> </TouchableOpacity> <TouchableOpacity onPress={() => { AsyncStorage.setItem('daiji','5555555555'); AsyncStorage.getItem('daiji').then((value) => { alert(value); }); }}> <View style={styles.findView}> <Image source={require('./icon/ic_code_black_24dp.png')} style={styles.findViewIcon} /> <Text style={styles.findViewText}>存储测试</Text> </View> </TouchableOpacity> </View> ); } } //登录组件 class LoginScreen extends React.Component{ constructor(props) { super(props); this.state = { username:'', password:'' }; }; render(){ return ( <View style={styles.container}> <View style={{padding:10,justifyContent:'center',flex:1}}> <View style={{flex:1}}> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({username:text})} underlineColorAndroid='transparent' maxLength={8} placeholder='用户名' /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({password:text})} underlineColorAndroid='transparent' placeholder='密码' maxLength={20} secureTextEntry={true} /> <Button onPress={()=>{ if(this.state.username == ''){ ToastAndroid.show("用户名不能为空",ToastAndroid.SHORT); return false; } if(this.state.password == ''){ ToastAndroid.show("密码不能为空",ToastAndroid.SHORT); return false; } alert(this.state.username + '||' + this.state.password); }} color='#264D7F' title="登录" /> </View> <View style={{flex:2,flexDirection:'row'}}> <View style={{flex:1}}> <TouchableOpacity onPress={() => {this.props.navigation.navigate('reg');}}> <Text style={{color:'#264D7F'}}>我要注册</Text> </TouchableOpacity> </View> <View style={{flex:1,alignItems:'flex-end'}}> <TouchableOpacity onPress={() => {this.props.navigation.navigate('forgotEmail');}}> <Text style={{color:'#264D7F'}}>忘记密码</Text> </TouchableOpacity> </View> </View> </View> </View> ); } } //注册组件 class RegScreen extends React.Component{ constructor(props) { super(props); this.state = { username:'', password:'', password2:'', email:'' }; }; render(){ return ( <View style={styles.container}> <View style={{padding:10,justifyContent:'center',flex:1}}> <View style={{flex:1}}> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({username:text})} underlineColorAndroid='transparent' maxLength={8} placeholder='用户名' /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({password:text})} underlineColorAndroid='transparent' placeholder='密码' maxLength={20} secureTextEntry={true} /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({password2:text})} underlineColorAndroid='transparent' placeholder='确认密码' maxLength={20} secureTextEntry={true} /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({email:text})} underlineColorAndroid='transparent' maxLength={30} keyboardType='email-address' placeholder='邮箱' /> <Button onPress={()=>{ if(this.state.username == ''){ ToastAndroid.show("用户名不能为空",ToastAndroid.SHORT); return false; } if(this.state.password == ''){ ToastAndroid.show("密码不能为空",ToastAndroid.SHORT); return false; } if(this.state.password2 == ''){ ToastAndroid.show("确认密码不能为空",ToastAndroid.SHORT); return false; } if(this.state.password2 != this.state.password){ ToastAndroid.show("两次密码输入不一致",ToastAndroid.SHORT); return false; } if(this.state.email == ''){ ToastAndroid.show("邮箱不能为空",ToastAndroid.SHORT); return false; } alert(this.state.username + '||' + this.state.password); }} color='#264D7F' title="注册" /> <Text style={{marginTop:10,color:'#C30D23',fontSize:16}}>555555555555555555555555</Text> </View> </View> </View> ); } } // 忘记密码,之 填写邮箱 class ForGotEmailScreen extends React.Component{ constructor(props) { super(props); this.state = { email:'' }; }; render(){ return ( <View style={styles.container}> <View style={{padding:10,justifyContent:'center',flex:1}}> <View style={{flex:1}}> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({email:text})} underlineColorAndroid='transparent' maxLength={30} keyboardType='email-address' placeholder='邮箱' /> <Button onPress={()=>{ if(this.state.email == ''){ ToastAndroid.show("邮箱不能为空",ToastAndroid.SHORT); return false; } this.props.navigation.navigate('forgotReset'); }} color='#264D7F' title="获取验证码" /> <Text style={{marginTop:10,color:'#C30D23',fontSize:16}}>555555555555555555555555</Text> </View> </View> </View> ); } } // 忘记密码之重设密码 class ForGotResetScreen extends React.Component{ constructor(props) { super(props); this.state = { password:'', password2:'', code:'' }; }; render(){ return ( <View style={styles.container}> <View style={{padding:10,justifyContent:'center',flex:1}}> <View style={{flex:1}}> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({code:text})} underlineColorAndroid='transparent' maxLength={8} placeholder='验证码' /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({password:text})} underlineColorAndroid='transparent' placeholder='密码' maxLength={20} secureTextEntry={true} /> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderBottomWidth: 1,paddingLeft:10,marginBottom:20}} onChangeText={(text) => this.setState({password2:text})} underlineColorAndroid='transparent' placeholder='确认密码' maxLength={20} secureTextEntry={true} /> <Button onPress={()=>{ if(this.state.code == ''){ ToastAndroid.show("验证码不能为空",ToastAndroid.SHORT); return false; } if(this.state.password == ''){ ToastAndroid.show("密码不能为空",ToastAndroid.SHORT); return false; } if(this.state.password2 == ''){ ToastAndroid.show("确认密码不能为空",ToastAndroid.SHORT); return false; } if(this.state.password2 != this.state.password){ ToastAndroid.show("两次密码输入不一致",ToastAndroid.SHORT); return false; } alert( this.state.password); }} color='#264D7F' title="重置密码" /> <Text style={{marginTop:10,color:'#C30D23',fontSize:16}}>555555555555555555555555</Text> </View> </View> </View> ); } } //定义tab组件 const TabScreen = TabNavigator({ dynamic: { screen: DynamicScreen }, find: { screen: FindScreen }, my:{ screen:MyScreen } },{ tabBarPosition: 'bottom',//选项卡位置 animationEnabled: true, tabBarOptions: { activeTintColor: '#72A7F7',//选中颜色 inactiveTintColor:'#919191',//未选中颜色 //设置选项卡的背景颜色 style: { backgroundColor: '#FFFFFF', borderTopWidth:1, borderTopColor:'#E3E3E3' }, //去掉安卓点击之后的小黄线 indicatorStyle: { height: 0 }, //是否显示icon图标 showIcon:true, showLabel:false, } }); //组件样式 const styles = StyleSheet.create({ container: { flex: 1, backgroundColor:'#EBEBEB' }, item: { flex:1, flexDirection:'row', padding:10, backgroundColor:'#fff', }, title:{ color:'#4D4F53', fontSize:18 }, info:{ color:'#4d4f53', fontSize:14, marginTop:5, fontWeight:'400' }, time:{ color:'gray', fontSize:12, marginTop:8 }, //动态组件图片 userphoto:{ width:40, height:40, tintColor:'#9E9E9E' }, //头部组件+ headAdd:{ width: 24, height: 24, tintColor: '#FFFFFF', marginRight:20, }, //发现组件样式 findView:{ backgroundColor:'#fff', marginTop:15, padding:10, alignItems:'center', flexDirection:'row', }, findViewText2:{ flex:8, }, findViewIcon:{ width:24, height:24, marginRight:15 }, findViewIcon2:{ flex:2, width:46, height:46, }, findViewText:{ fontSize:20, color:'#232323', fontWeight:'400' }, findBody:{ backgroundColor:'#EBEBEB' }, findMT:{ marginTop:0, borderTopWidth:1, borderTopColor:'#EBEBEB' }, headerBar:{ backgroundColor:'#264D7F', }, headerTitle:{ color:'#FFFFFF', fontSize:16 } }); class HeadScreen extends React.Component{ constructor(props) { super(props); } render(){ return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <TouchableOpacity onPress={() => { this.props.navigation.navigate('search'); }}> <Image source={require('./icon/ic_search_black_24dp.png')} style={styles.headAdd} /> </TouchableOpacity> </View> ); } } //搜索组件 class SearchScreen extends React.Component{ constructor(props) { super(props); this.state = { text: '',data:[]}; this.requestData(); } requestData = () => { const url = apiUrl + "/search.php"; fetch(url).then(res => { return res.json(); }).then(res => { this.setState({ data: [...this.state.data, ...res], }); }).catch(err => { ToastAndroid.show('获取数据失败',ToastAndroid.SHORT); }); } render(){ return ( <View style={[styles.container,{backgroundColor:'#fff'}]}> <View style={{padding:10,flex:1,justifyContent:'center'}}> <View style={{flex:1}}> <TextInput style={{height: 34, borderColor: '#264D7F', marginTop:1,borderWidth: 1,paddingLeft:10}} onChangeText={(text) => this.setState({text})} underlineColorAndroid='transparent' placeholder='输入关键字' /> </View> <View style={{flex:1}}> <Button onPress={()=>{ if(this.state.text == ''){ ToastAndroid.show("您好像没有输入任何关键字",ToastAndroid.SHORT); return false; } this.props.navigation.navigate('dynamicDetail',{url:'https://www.baidu.com/s?wd=' + this.state.text,title:this.state.text + '-搜索结果'}); }} color='#264D7F' title="搜一下" /> </View> </View> <View style={{flex:5}}> <FlatList data={this.state.data} keyExtractor={item => item.id} renderItem={({item}) => ( <TouchableOpacity onPress={() => this.props.navigation.navigate('dynamicDetail',{title:item.title})}> <View style={styles.item}> <View style={{flex:1}}> <Text style={styles.title}>{item.title}</Text> <Text style={styles.info}>xxxxxxxxxxxxxx</Text> <Text style={styles.time}>2017</Text> </View> </View> </TouchableOpacity> )} ItemSeparatorComponent={ ()=>( <View style={{backgroundColor:'#EBEBEB',height:5}}> </View> ) } ListHeaderComponent={ ()=>( <View style={{height:40,justifyContent:'center',alignItems:'center',backgroundColor:"#EBEBEB"}}> <Text>大家都在搜</Text> </View> ) } /> </View> </View> ); } } // myapp路由 const MyApp = StackNavigator({ dynamicStack: { screen: TabScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', headerRight:(<HeadScreen navigation={navigation}></HeadScreen>), }), }, dynamicDetail: { screen: DynamicDetailScreen, }, search:{ screen:SearchScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', title:'搜一搜' }), }, login:{ screen:LoginScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', title:'用户登录' }), }, reg:{ screen:RegScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', title:'用户注册' }), }, forgotEmail:{ screen:ForGotEmailScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', title:'获取验证码' }), }, forgotReset:{ screen:ForGotResetScreen, navigationOptions: ({navigation}) => ({ headerStyle:styles.headerBar, headerTitleStyle:styles.headerTitle, headerTintColor:'#FFFFFF', title:'重置密码' }), } }); export default class App extends React.Component { render() { return <MyApp />; } } ~~~