基于Flutter實(shí)現(xiàn)圖片選擇和圖片上傳
內(nèi)容簡(jiǎn)介
本篇將介紹 Flutter 中如何完成圖片上傳,以及上傳成功后的表單提交。涉及的知識(shí)點(diǎn)如下:
- 圖片選擇插件
wechat_assets_picker
的使用。 - 圖片選擇 iOS 和安卓的應(yīng)用權(quán)限配置。
- 圖片選擇組件的封裝。
- 圖片上傳接口的封裝。
- 添加和編輯頁(yè)面中圖片上傳實(shí)現(xiàn)。
圖片選擇插件
Flutter 的圖片選擇插件很多,包括了官方的 image_picker
,multi_image_picker
(基于2.0出了 multi_image_picker2
)等等。為了尋找合適的圖片選擇插件,找了好幾個(gè),發(fā)現(xiàn)了一個(gè)仿微信的圖片選擇插件 wechat_assets_picker
,看評(píng)分和 Github的Star都不錯(cuò),先來(lái)試用一下。
權(quán)限申請(qǐng)
先上了一個(gè)簡(jiǎn)單的 demo,直接調(diào)用:
final?List<AssetEntity>?assets?=?await?AssetPicker.pickAssets(context);
結(jié)果發(fā)現(xiàn)閃退了!??!難道是插件有bug?
bug.jpg
哦,想起來(lái)了!忘記設(shè)置圖片獲取權(quán)限了!iOS 在 Runner
的 Info.plist
文件增加如下內(nèi)容:
<key>NSAppTransportSecurity</key> <dict> ??<key>NSAllowsArbitraryLoads</key> ??<true/> </dict> <key>NSPhotoLibraryUsageDescription</key> <string>獲取圖片及使用相冊(cè)拍照以便上傳動(dòng)態(tài)圖片。</string>
安卓在app/profile/AndroidManifest.xml
和 app/debug/AndroidManifest.xml
中增加如下內(nèi)容:
<uses-permission?android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission?android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission?android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
再次運(yùn)行,完美!我們都是這么跑 Demo 的不是?
選擇圖片.jpg
UI 改造
我們將動(dòng)態(tài)的添加和編輯修改為選擇圖片的方式,原先的輸入框不能用了,需要更改為圖片選擇,考慮圖片選擇會(huì)經(jīng)常用,封裝一個(gè)通用的單圖選擇組件。
static?Widget?imagePicker( ??String?formKey, ??ValueChanged<String>?onTapped,?{ ??File?imageFile, ??String?imageUrl, ??double?width?=?80.0, ??double?height?=?80.0, })?{ ??return?GestureDetector( ????child:?Container( ??????margin:?EdgeInsets.all(10), ??????alignment:?Alignment.center, ??????decoration:?BoxDecoration( ????????color:?Colors.grey[300], ????????border:?Border.all(width:?0.5,?style:?BorderStyle.solid), ????????borderRadius:?BorderRadius.all(Radius.circular(4.0)), ??????), ??????child:?_getImageWidget(imageFile,?imageUrl,?width,?height), ??????width:?width, ??????height:?height, ????), ????onTap:?()?{ ??????onTapped(); ????}, ??); } static?Widget?_getImageWidget( ????File?imageFile,?String?imageUrl,?double?width,?double?height)?{ ??if?(imageFile?!=?null)?{ ????return?Image.file( ??????imageFile, ??????fit:?BoxFit.cover, ??????width:?width, ??????height:?height, ????); ??} ??if?(imageUrl?!=?null)?{ ????return?CachedNetworkImage( ??????imageUrl:?imageUrl, ??????fit:?BoxFit.cover, ??????width:?width, ??????height:?height, ????); ??} ??return?Icon(Icons.add_photo_alternate); }
這里考慮圖片選擇組件的占位圖片可能來(lái)自網(wǎng)絡(luò),也可能是文件,因此做了不同的處理。優(yōu)先顯示文件圖片,其次是網(wǎng)絡(luò)圖片,若都沒(méi)有則顯示一個(gè)添加圖片的圖標(biāo)。
之前的動(dòng)態(tài)表單 dynamic_form.dart
也需要進(jìn)行相應(yīng)的調(diào)整,包括接收?qǐng)D片參數(shù),圖片處理函數(shù),并且將之前的圖片文本框改為圖片選擇組件,點(diǎn)擊該組件時(shí)調(diào)用wechat_assets_picker
插件提供的AssetPicker.pickAssets
方法,限制最大可選則圖片為1張。
List<Widget>?_getForm(BuildContext?context)?{ ??List<Widget>?widgets?=?[]; ??formData.forEach((key,?formParams)?{ ????widgets.add(FormUtil.textField(key,?formParams['value'], ????????controller:?formParams['controller']????null, ????????hintText:?formParams['hintText']????'', ????????prefixIcon:?formParams['icon'], ????????onChanged:?handleTextFieldChanged, ????????onClear:?handleClear)); ??}); ??widgets.add(FormUtil.imagePicker( ????'imageUrl', ????()?{ ??????_pickImage(context); ????}, ????imageFile:?imageFile, ????imageUrl:?imageUrl, ??)); ??widgets.add(ButtonUtil.primaryTextButton( ????buttonName, ????handleSubmit, ????context, ????width:?MediaQuery.of(context).size.width?-?20, ??)); ??return?widgets; } void?_pickImage(BuildContext?context)?async?{ ??final?List<AssetEntity>?assets?= ??????await?AssetPicker.pickAssets(context,?maxAssets:?1); ??if?(assets.length?>?0)?{ ????File?file?=?await?assets[0].file; ????handleImagePicked(file); ??} }
看看效果怎么樣?看起來(lái)一切正常,接下來(lái)看如何上傳。
屏幕錄制2021-07-10 下午4.51.51.gif
圖片上傳
圖片上傳和獲取接口之前已經(jīng)完成,可以先拉取最新的后臺(tái)代碼:基于 ExpressJs 的后臺(tái)代碼。接口地址分別為:
- 單張圖片上傳接口地址:http://localhost:3900/api/upload/image ,Post 請(qǐng)求,字段名為
image
,成功后返回圖片文件id
。 - 圖片獲取接口:http://localhost:3900/api/upload/image/:id ,利用圖片文件
id
即可獲取圖片文件流。
Dio 提供了FormData
的方式上傳文件,示例代碼如下:
//?單個(gè)文件上傳 var?formData?=?FormData.fromMap({ ??'name':?'wendux', ??'age':?25, ??'file':?await?MultipartFile.fromFile('./text.txt',filename:?'upload.txt') }); response?=?await?dio.post('/info',?data:?formData); //?多個(gè)文件上傳 FormData.fromMap({ ??'files':?[ ????MultipartFile.fromFileSync('./example/upload.txt',?filename:?'upload.txt'), ????MultipartFile.fromFileSync('./example/upload.txt',?filename:?'upload.txt'), ??] });
我們可以利用這種方式完成圖片的上傳。圖片上傳屬于一個(gè)公共的服務(wù),我們新建一個(gè) upload_service.dart
文件,用于管理所有上傳接口。當(dāng)前只有一個(gè)上傳單個(gè)文件的方法,從圖片文件獲取文件路徑構(gòu)建 MultipartFile
對(duì)象即可,如下所示。
import?'dart:io'; import?'package:dio/dio.dart'; class?UploadService?{ ??static?const?String?uploadBaseUrl?=?'http://localhost:3900/api/upload/'; ??static?Future?uploadImage(String?key,?File?file)?async?{ ????FormData?formData?= ????????FormData.fromMap({key:?await?MultipartFile.fromFile(file.path)}); ????var?result?=?await?Dio().post(uploadBaseUrl?+?'image',?data:?formData); ????return?result; ??} }
接下來(lái)就是處理提交事件了,這里添加和編輯處理邏輯會(huì)有些不同:
- 添加時(shí)需要校驗(yàn)圖片文件是否為空,為空則提示需要上傳文件;
- 編輯時(shí),本身是一個(gè) 原數(shù)據(jù)的Url,若圖片文件為空,此時(shí)不需要向后臺(tái)上傳圖片,也不需要將圖片原有的 Url上傳 (后臺(tái)代碼只存儲(chǔ)圖片文件的 id,由前端拼接完整地址)。若圖片文件不為空,則需要提交數(shù)據(jù)到后臺(tái)。如果做得體驗(yàn)更優(yōu)和節(jié)省后端請(qǐng)求,可以比較新表單數(shù)據(jù)和原表數(shù)據(jù)是否相同,若沒(méi)有改變則無(wú)需提交數(shù)據(jù)請(qǐng)求。
提交時(shí),我們需要先上傳圖片,圖片上傳成功后將圖片文件 id
放入到提交的表單數(shù)據(jù)里在提交新增或更新接口中。添加時(shí)的提交代碼如下所示:
_handleSubmit()?async?{ ??//其他表單校驗(yàn) ??if?(_imageFile?==?null)?{ ????Dialogs.showInfo(this.context,?'圖片不能為空'); ????return; ??} ??EasyLoading.showInfo('請(qǐng)稍候...',?maskType:?EasyLoadingMaskType.black); ??try?{ ????String?imageId; ????var?imageResponse?=?await?UploadService.uploadImage('image',?_imageFile); ????if?(imageResponse.statusCode?==?200)?{ ??????imageId?=?imageResponse.data['id']; ????} ????if?(imageId?==?null)?{ ??????Dialogs.showInfo(this.context,?'圖片上傳失敗'); ??????return; ????} ????Map<String,?String>?newFormData?=?{}; ????_formData.forEach((key,?value)?{ ??????newFormData[key]?=?value['value']; ????}); ????//新增時(shí)將圖片?id?放入提交表單中 ????newFormData['imageUrl']?=?imageId; ????//?省略提交代碼 ??} ??//?... ??//省略異常處理代碼 }
以上就是基于Flutter實(shí)現(xiàn)圖片選擇和圖片上傳的詳細(xì)內(nèi)容,更多關(guān)于Flutter圖片選擇上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
OnSharedPreferenceChangeListener詳解及出現(xiàn)不觸發(fā)解決辦法
本文主要介紹 Android OnSharedPreferenceChangeListener的知識(shí),在Android應(yīng)用開(kāi)發(fā)過(guò)程中會(huì)遇到監(jiān)聽(tīng)器不觸發(fā)事件問(wèn)題,這里介紹了相應(yīng)的解決辦法2016-08-08Flutter實(shí)現(xiàn)密碼強(qiáng)度校驗(yàn)結(jié)果的示例詳解
我們經(jīng)常在一些網(wǎng)站上看到這樣的密碼強(qiáng)度指示,使用三段線,分別用不同的顏色來(lái)表示弱密碼、中等強(qiáng)度密碼和強(qiáng)密碼,本篇我們就用?Flutter?來(lái)實(shí)現(xiàn)這樣一個(gè)密碼強(qiáng)度校驗(yàn)示例,希望對(duì)大家有所幫助2023-08-08詳解Android中visibility屬性VISIBLE、INVISIBLE、GONE的區(qū)別
在Android開(kāi)發(fā)中,大部分控件都有visibility這個(gè)屬性,其屬性有3個(gè)分別為“visible ”、“invisible”、“gone”。主要用來(lái)設(shè)置控制控件的顯示和隱藏。本文就詳細(xì)的講解一下。2016-12-12Android實(shí)現(xiàn)跟隨手指拖動(dòng)并自動(dòng)貼邊的View樣式(實(shí)例demo)
本文通過(guò)實(shí)例代碼給大家介紹了android實(shí)現(xiàn)跟隨手指拖動(dòng)并自動(dòng)貼邊的View樣式,效果非常棒,具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01Android編程實(shí)現(xiàn)對(duì)話框Dialog背景透明功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)對(duì)話框Dialog背景透明功能,涉及Android對(duì)話框的布局、屬性及事件處理相關(guān)操作技巧,需要的朋友可以參考下2017-07-07Android開(kāi)發(fā)Viewbinding委托實(shí)例詳解
這篇文章主要為大家介紹了Android開(kāi)發(fā)Viewbinding委托實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼,需要的朋友可以參考下2017-09-09Android 獲取屏幕高度,標(biāo)題高度,狀態(tài)欄高度(實(shí)例代碼)
getWindow().findViewById(Window.ID_ANDROID_CONTENT)這個(gè)方法獲取到的view就是程序不包括標(biāo)題欄的部分,然后就可以知道標(biāo)題欄的高度了2013-11-11