照相显示因服务器,Flutter 相机拍照并上传到服务器

田螺收纳 app 需要拍摄衣物图片并上传服务器,研究了一波 Flutter 操作相机的细节,本文为实践笔记。

引入第三方库

编辑 pubspec.yaml 文件,添加第三库:

dependencies:

camera:

path_provider:

path:

camera 获取手机上的相机,返回值是个数组,手机一般自带一个相机,还可以下载第三方相机如美颜相机等;

path_provider 提供路徑來儲存照片;

path 建立一個可以在使用任何裝置的路徑。

实现拍照功能

想一想我们如何使用 app 进行拍照,我们通常会点击拍照按钮,之后会跳转到一个拍照的页面。

编写拍照页面

如下是一个普通的拍照页面,你可以直接将它复制到您的项目中,也可以在阅读此页面源码后进行局部定制。

拍照页面接收两个参数:

camera 您需要给拍照页面传递一个相机。如前文所述,一台手机上可能会有多个可使用的相机(手机自带相机、从应用商店下载的美颜相机);

onOk 当您点击确认拍照时,在这个方法里编写需要做哪些事,如将拍照的图片上传到服务器或直接预览图片等。

import 'dart:io';

import 'package:camera/camera.dart';

import 'package:flutter/material.dart';

import 'package:path/path.dart';

import 'package:path_provider/path_provider.dart';

class TakePictureScreen extends StatefulWidget {

final CameraDescription camera;

final Function onOk;

const TakePictureScreen({

Key key,

@required this.camera,

@required this.onOk,

}) : super(key: key);

@override

TakePictureScreenState createState() => TakePictureScreenState();

}

class TakePictureScreenState extends State {

CameraController _controller;

Future _initializeControllerFuture;

String _imagePath;

@override

void initState() {

super.initState();

_controller = CameraController(

widget.camera,

ResolutionPreset.medium,

);

_initializeControllerFuture = _controller.initialize();

}

@override

void dispose() {

_controller.dispose();

super.dispose();

}

@override

Widget build(BuildContext context) {

if (_imagePath != null) {

return Scaffold(

body: Stack(

fit: StackFit.expand,

children: [

Image.file(File(_imagePath)),

Positioned(

bottom: 20.0,

left: 20.0,

child: Container(

decoration: new BoxDecoration(

border:

new Border.all(color: Colors.grey, width: 0.5), // 边色与边宽度

color: Colors.white, // 底色

shape: BoxShape.circle, // 圆形,使用圆形时不可以使用borderRadius

),

alignment: Alignment.center,

child: IconButton(

icon: Icon(Icons.replay),

onPressed: () {

setState(() {

_imagePath = null;

});

},

),

),

),

Positioned(

bottom: 20.0,

right: 20.0,

child: Container(

decoration: new BoxDecoration(

border:

new Border.all(color: Colors.grey, width: 0.5), // 边色与边宽度

color: Colors.white, // 底色

shape: BoxShape.circle, // 圆形,使用圆形时不可以使用borderRadius

),

alignment: Alignment.center,

child: IconButton(

icon: Icon(Icons.done),

onPressed: () {

widget.onOk(context, _imagePath);

},

),

),

),

],

),

);

}

return Scaffold(

body: FutureBuilder(

future: _initializeControllerFuture,

builder: (context, snapshot) {

if (snapshot.connectionState == ConnectionState.done) {

return CameraPreview(_controller);

} else {

return Center(child: CircularProgressIndicator());

}

},

),

floatingActionButton: FloatingActionButton(

child: Icon(Icons.camera_alt),

onPressed: () async {

try {

await _initializeControllerFuture;

final String path = join(

(await getTemporaryDirectory()).path,

'${DateTime.now()}.png',

);

await _controller.takePicture(path);

setState(() {

_imagePath = path;

});

} catch (e) {

print(e);

}

},

),

);

}

}

点击按钮,获取相机,跳转到拍照页面

仅有拍照页面是无法完成拍照功能的,下面模拟在主页面点击按钮,我们需要获取手机上可用的相机,之后跳转到拍照页面。

onOk 方法接收两个参数:

context 拍照页面(TakePictureScreen)的上下文,用于处理完业务逻辑后手动调用 Navigator.pop(context) 退出拍照页面;

imagePath 在拍照页面进行拍摄后,照片会暂时存储在手机本地上,这个参数是存在手机上的路径;

IconButton(

icon: Icon(Icons.camera_alt),

onPressed: () async {

// 获取手机上可用的所有相机,返回值是个数组

final List cameras =

await availableCameras();

Navigator.push(

context,

MaterialPageRoute(

builder: (context) => TakePictureScreen(

camera: cameras.first, // 使用数组中的第一个相机

onOk: (BuildContext context, String imagePath) async {

// ......

},

),

),

);

},

),

将拍摄的图片上传到服务器

完善前一步骤的 onOk 函数体内容,使用 dio(v3.0.0) 网络请求库上传图片。

onOk: (BuildContext context, String imagePath) async {

// 上传图片

FormData formData = FormData.fromMap({

'photo': await MultipartFile.fromFile(imagePath,

filename: '${DateTime.now()}.png'),

});

Response result = await HttpUtils.getInstance().post(

'/upload',

data: formData,

options: Options(headers: {

'Authorization': 'Bearer ' + token,

}),

);

print('result = $result');

// 业务逻辑处理完,记得退出拍照页面

Navigator.pop(context);

},

可能出现的报错

启动项目,可能会出现一些错误,总结如下:

Unhandled Exception: MissingPluginException(No implementation found for method canLaunch on channel plugins.flutter.io/url_launcher)

字面意思是插件找不到。

造成这个报错的原因是:我们先启动了 Flutter 应用,之后再去 pubspec.yaml 文件添加 camera 插件,此时是无法生效的,会报插件丢失的错误。

解决方法:关闭 Flutter 应用,重新启动即可。

uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library

字面意思,第三方库 camera 要求 minSdkVersion 版本最低为 21,但是我们项目里自带的 minSdkVersion 版本是 16,不兼容导致报错。

解决方法:将项目的 minSdkVersion 版本号手动改为 21 即可。

修改方法:在根目录下找到 /android/app/build.gradle 文件:

defaultConfig {

applicationId "com.example.projectname"

minSdkVersion 21 // <===================== 更改成 21,之前的默认值是 16

targetSdkVersion 28

versionCode 1

versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

你可能感兴趣的:(照相显示因服务器)