image_picker更多参考在https://pub.dev/packages/image_picker
在配置文件pubspec.yaml添加如下配置:
dependencies:
flutter:
sdk: flutter
image_picker: ^0.6.7
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(DemoApp());
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Image Picker Demo',
home: new MyHomePage(
title: "Image Picker Example",
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/// 图片选择器
final ImagePicker _picker = ImagePicker();
/// 选择的图片
PickedFile _imageFile;
/// 是否是视频
bool isVideo = false;
/// 定义错误信息
String _retrieveDataError;
/// 选择图片时的错误信息
dynamic _pickImageError;
/// 视频播放器控制器
VideoPlayerController _controller;
VideoPlayerController _toBeDisposed;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android
? FutureBuilder(
future: retrieveLostData(),
builder:
(BuildContext context, AsyncSnapshot<void> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
case ConnectionState.done:
return isVideo ? _previewVideo() : _previewImage();
default:
if (snapshot.hasError) {
return Text(
"Pick image/video error:${snapshot.error}",
textAlign: TextAlign.center,
);
} else {
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
}
}
})
: (isVideo ? _previewVideo() : _previewImage())),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
/// 选择图片
isVideo = false;
_onImageButtonPressed(ImageSource.gallery, context: context);
},
child: const Icon(Icons.photo_library),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
/// 拍照
isVideo = false;
_onImageButtonPressed(ImageSource.camera, context: context);
},
child: const Icon(Icons.camera_alt),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
// 选择视频文件
isVideo = true;
_onImageButtonPressed(ImageSource.gallery);
},
backgroundColor: Colors.red,
child: const Icon(Icons.video_library),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
// 拍摄
isVideo = true;
_onImageButtonPressed(ImageSource.camera);
},
backgroundColor: Colors.red,
child: const Icon(Icons.videocam),
),
),
],
),
);
}
/// 获得丢失的数据
Future<void> retrieveLostData() async {
final LostData response = await _picker.getLostData();
if (response.isEmpty) {
return;
}
if (response.file != null) {
if (response.type == RetrieveType.video) {
/// video
isVideo = true;
/// 播放视频
await _playVideo(response.file);
} else {
/// 图片
isVideo = false;
setState(() {
/// 更新UI,把图片显示出来
_imageFile = response.file;
});
}
} else {
_retrieveDataError = response.exception.code;
}
}
/// 播放视频
Future<void> _playVideo(PickedFile file) async {
/// mounted true 表示当前state仍然在widget tree上
if (file != null && mounted) {
/// 重置视频播放器控制器
await _disposeVideoController();
if (kIsWeb) {
/// kIsWeb true表示是web应用
_controller = VideoPlayerController.network(file.path);
/// 设置音量为0
await _controller.setVolume(0.0);
} else {
/// 非web应用
_controller = VideoPlayerController.file(File(file.path));
/// 设置音量为1.0
await _controller.setVolume(1.0);
}
await _controller.initialize();
await _controller.setLooping(true);
await _controller.play();
/// 更新UI
setState(() {});
}
}
/// 重置视频播放器控制器
Future<void> _disposeVideoController() async {
if (_toBeDisposed != null) {
await _toBeDisposed.dispose();
}
_toBeDisposed = _controller;
_controller = null;
}
Text _getRetrieveErrorWidget() {
if (_retrieveDataError != null) {
final Text result = Text(_retrieveDataError);
_retrieveDataError = null;
return result;
}
return null;
}
/// 预览视频
Widget _previewVideo() {
/// 获取错语信息
final Text retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
/// 有错误信息则显示错误信息
return retrieveError;
}
if (_controller == null) {
/// 如果视频播放器控制器为空,则提示用户选择视频
return const Text(
"You have not yet picked a vide",
textAlign: TextAlign.center,
);
}
return Padding(
padding: const EdgeInsets.all(10.0),
child: AspectRatioVideo(_controller),
);
}
/// 预览图片
Widget _previewImage() {
/// 获取错语信息
final Text retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
/// 有错误信息则显示错误信息
return retrieveError;
}
if (_imageFile != null) {
if (kIsWeb) {
/// web应用
return Image.network(_imageFile.path);
} else {
/// 非web应用
return Image.file(File(_imageFile.path));
}
} else if (_pickImageError != null) {
return Text(
"Pick image error: $_pickImageError",
textAlign: TextAlign.center,
);
} else {
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
}
}
/// 选择图片、视频,拍摄视频、照片
void _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
if (_controller != null) {
/// 让其静音
await _controller.setVolume(0.0);
}
if (isVideo) {
/// 选择或拍摄视频的处理
/// 限制视频为10秒
final PickedFile file = await _picker.getVideo(
source: source, maxDuration: Duration(seconds: 10));
/// 返回到此界面时就播放
await _playVideo(file);
} else {
/// 选择或拍摄图片
/// 图片的参数设置对话框
await _displayPickImageDialog(context,
(double maxWidth, double maxHeight, int quality) async {
try {
final pickedFile = await _picker.getImage(
source: source,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality);
setState(() {
_imageFile = pickedFile;
});
} catch (e) {
_pickImageError = e;
}
});
}
}
final TextEditingController maxWidthController = TextEditingController();
final TextEditingController maxHeightController = TextEditingController();
final TextEditingController qualityController = TextEditingController();
/// 图片的参数设置对话框
Future<void> _displayPickImageDialog(
BuildContext context, OnPickImageCallback onPick) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Add optional parameters"),
content: Column(
children: <Widget>[
TextField(
controller: maxWidthController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration:
InputDecoration(hintText: "Enter maxWidth if desired"),
),
TextField(
controller: maxHeightController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration:
InputDecoration(hintText: "Enter maxHeight if desired"),
),
TextField(
controller: qualityController,
keyboardType: TextInputType.number,
decoration:
InputDecoration(hintText: "Enter quality if desired"),
),
],
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("CANCEL"),
),
FlatButton(
onPressed: () {
double width = maxWidthController.text.isNotEmpty
? double.parse(maxWidthController.text)
: null;
double height = maxHeightController.text.isNotEmpty
? double.parse(maxHeightController.text)
: null;
int quality = qualityController.text.isNotEmpty
? int.parse(qualityController.text)
: null;
onPick(width, height, quality);
Navigator.of(context).pop();
},
child: Text("PICK"),
),
],
);
},
);
}
@override
void deactivate() {
if (_controller != null) {
_controller.setVolume(0.0);
_controller.pause();
}
super.deactivate();
}
@override
void dispose() {
_disposeVideoController();
maxWidthController.dispose();
maxHeightController.dispose();
qualityController.dispose();
super.dispose();
}
}
typedef void OnPickImageCallback(
double maxWidth, double maxHeight, int quality);
/// 自定义一个Widget,用于播放视频
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
@override
_AspectRatioVideoState createState() => _AspectRatioVideoState();
}
class _AspectRatioVideoState extends State<AspectRatioVideo> {
/// 只读
VideoPlayerController get controller => widget.controller;
bool initialized = false;
@override
void initState() {
super.initState();
controller.addListener(_onVideoControllerUpdate);
}
/// 更新
void _onVideoControllerUpdate() {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
/// 更新UI
setState(() {});
}
}
@override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
child: VideoPlayer(controller),
aspectRatio: controller.value?.aspectRatio,
),
);
} else {
return Container();
}
}
/// 关闭时,清理工作
@override
void dispose() {
controller.removeListener(_onVideoControllerUpdate);
super.dispose();
}
}
如果有图片显示,有视频则播放视频,否则提示用户选择图片:
body:Center(
child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android
? FutureBuilder(
future: retrieveLostData(),
builder:
(BuildContext context, AsyncSnapshot<void> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
case ConnectionState.done:
return isVideo ? _previewVideo() : _previewImage();
default:
if (snapshot.hasError) {
return Text(
"Pick image/video error:${snapshot.error}",
textAlign: TextAlign.center,
);
} else {
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
}
}
})
: (isVideo ? _previewVideo() : _previewImage())),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
/// 选择图片
isVideo = false;
_onImageButtonPressed(ImageSource.gallery, context: context);
},
child: const Icon(Icons.photo_library),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
/// 拍照
isVideo = false;
_onImageButtonPressed(ImageSource.camera, context: context);
},
child: const Icon(Icons.camera_alt),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
// 选择视频文件
isVideo = true;
_onImageButtonPressed(ImageSource.gallery);
},
backgroundColor: Colors.red,
child: const Icon(Icons.video_library),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton(
onPressed: () {
// 拍摄
isVideo = true;
_onImageButtonPressed(ImageSource.camera);
},
backgroundColor: Colors.red,
child: const Icon(Icons.videocam),
),
),
],
),
/// 选择图片、视频,拍摄视频、照片
void _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
if (_controller != null) {
/// 让其静音
await _controller.setVolume(0.0);
}
if (isVideo) {
/// 选择或拍摄视频的处理
/// 限制视频为10秒
final PickedFile file = await _picker.getVideo(
source: source, maxDuration: Duration(seconds: 10));
/// 返回到此界面时就播放
await _playVideo(file);
} else {
/// 选择或拍摄图片
/// 图片的参数设置对话框
await _displayPickImageDialog(context,
(double maxWidth, double maxHeight, int quality) async {
try {
final pickedFile = await _picker.getImage(
source: source,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality);
setState(() {
_imageFile = pickedFile;
});
} catch (e) {
_pickImageError = e;
}
});
}
}
final TextEditingController maxWidthController = TextEditingController();
final TextEditingController maxHeightController = TextEditingController();
final TextEditingController qualityController = TextEditingController();
/// 图片的参数设置对话框
Future<void> _displayPickImageDialog(
BuildContext context, OnPickImageCallback onPick) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Add optional parameters"),
content: Column(
children: <Widget>[
TextField(
controller: maxWidthController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration:
InputDecoration(hintText: "Enter maxWidth if desired"),
),
TextField(
controller: maxHeightController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration:
InputDecoration(hintText: "Enter maxHeight if desired"),
),
TextField(
controller: qualityController,
keyboardType: TextInputType.number,
decoration:
InputDecoration(hintText: "Enter quality if desired"),
),
],
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("CANCEL"),
),
FlatButton(
onPressed: () {
double width = maxWidthController.text.isNotEmpty
? double.parse(maxWidthController.text)
: null;
double height = maxHeightController.text.isNotEmpty
? double.parse(maxHeightController.text)
: null;
int quality = qualityController.text.isNotEmpty
? int.parse(qualityController.text)
: null;
onPick(width, height, quality);
Navigator.of(context).pop();
},
child: Text("PICK"),
),
],
);
},
);
}
/// 播放视频
Future<void> _playVideo(PickedFile file) async {
/// mounted true 表示当前state仍然在widget tree上
if (file != null && mounted) {
/// 重置视频播放器控制器
await _disposeVideoController();
if (kIsWeb) {
/// kIsWeb true表示是web应用
_controller = VideoPlayerController.network(file.path);
/// 设置音量为0
await _controller.setVolume(0.0);
} else {
/// 非web应用
_controller = VideoPlayerController.file(File(file.path));
/// 设置音量为1.0
await _controller.setVolume(1.0);
}
await _controller.initialize();
await _controller.setLooping(true);
await _controller.play();
/// 更新UI
setState(() {});
}
}
实际上,上面这个方法只是提前设置好视播放器控制器的信息,然后刷新UI,最终在build方法里通过_previewVideo来将视频显示出来:
/// 预览视频
Widget _previewVideo() {
/// 获取错语信息
final Text retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
/// 有错误信息则显示错误信息
return retrieveError;
}
if (_controller == null) {
/// 如果视频播放器控制器为空,则提示用户选择视频
return const Text(
"You have not yet picked a vide",
textAlign: TextAlign.center,
);
}
return Padding(
padding: const EdgeInsets.all(10.0),
child: AspectRatioVideo(_controller),
);
}
为了显示视频,专门写了一个Widget控件AspectRatioVideo来显示:
/// 自定义一个Widget,用于播放视频
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
@override
_AspectRatioVideoState createState() => _AspectRatioVideoState();
}
class _AspectRatioVideoState extends State<AspectRatioVideo> {
/// 只读
VideoPlayerController get controller => widget.controller;
bool initialized = false;
@override
void initState() {
super.initState();
controller.addListener(_onVideoControllerUpdate);
}
/// 更新
void _onVideoControllerUpdate() {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
/// 更新UI
setState(() {});
}
}
@override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
child: VideoPlayer(controller),
aspectRatio: controller.value?.aspectRatio,
),
);
} else {
return Container();
}
}
/// 关闭时,清理工作
@override
void dispose() {
controller.removeListener(_onVideoControllerUpdate);
super.dispose();
}
}
/// 预览图片
Widget _previewImage() {
/// 获取错语信息
final Text retrieveError = _getRetrieveErrorWidget();
if (retrieveError != null) {
/// 有错误信息则显示错误信息
return retrieveError;
}
if (_imageFile != null) {
if (kIsWeb) {
/// web应用
return Image.network(_imageFile.path);
} else {
/// 非web应用
return Image.file(File(_imageFile.path));
}
} else if (_pickImageError != null) {
return Text(
"Pick image error: $_pickImageError",
textAlign: TextAlign.center,
);
} else {
return const Text(
"You have not yet picked an image",
textAlign: TextAlign.center,
);
}
}
总的来说,使用flutter来写拍照、录视频等功能的体验真是太棒了!