以身份证识别(Face++的API接口实现方式:https://console.faceplusplus.com.cn/documents/5671702)为例,实现一个自定义相机的功能,实现效果:自定义相机页面、拍照、照片预览。
自定义相机页面效果展示
代码实现
GitHub地址(https://github.com/Lightforest/FlutterVideo)
1.在pubspec.yaml中添加插件:
camera(flutter官方插件:https://pub.dev/packages/camera)
2.图片资源准备
将身份证框图放在项目根目录下的assets目录中备用,并在pubspec.yaml中导入目录
3.页面布局实现
页面总布局代码如下:
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: new Container(
color: Colors.black,
child:new Stack(children: <Widget>[
new Column(children: <Widget>[
Expanded(
flex: 3,//flex用来设置当前可用空间的占优比
child: new Stack(children: <Widget>[
_cameraPreviewWidget(),//相机视图
_cameraFloatImage(),//悬浮的身份证框图
]),
),
Expanded(
flex: 1,//flex用来设置当前可用空间的占优比
child: _takePictureLayout(),//拍照操作区域布局
),
],),
getPhotoPreview(),//图片预览布局
getProgressDialog(),//数据加载中提示框
getRestartAlert(),// 身份证识别失败,重新拍照的提示按钮
]),
)
);
}
页面总布局共包括6部分的内容:相机预览、身份证框图、拍照按钮区域、拍照后的图片预览、识别身份证时的加载框、身份证识别失败的提示信息。
相机预览(cameraPreviewWidget())的代码如下:
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Tap a camera',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
fontWeight: FontWeight.w900,
),
);
} else {
return new Container(
width:double.infinity,
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
),
);
}
}
身份证框图(_cameraFloatImage())的代码如下:
Widget _cameraFloatImage(){
return new Positioned(
child: new Container(
alignment: Alignment.center,
margin: const EdgeInsets.fromLTRB(50, 50, 50, 50),
child:new Image.asset('assets/images/bg_identify_idcard.png'),
));
}
拍照区域(_takePictureLayout())的代码如下:
Widget _takePictureLayout(){
return new Align(
alignment: Alignment.bottomCenter,
child: new Container(
color: Colors.blueAccent,
alignment: Alignment.center,
child: new IconButton(
iconSize: 50.0,
onPressed: controller != null &&
controller.value.isInitialized &&
!controller.value.isRecordingVideo
? onTakePictureButtonPressed
: null,
icon: Icon(
Icons.photo_camera,
color: Colors.white,
),
),
));
}
拍照后的图片预览(getPhotoPreview())代码如下:
Widget getPhotoPreview(){
if( null != photoPath){
return new Container(
width:double.infinity,
height: double.infinity,
color: Colors.black,
alignment: Alignment.center,
child: Image.file(File(photoPath)),
);
}else{
return new Container(
height: 1.0,
width: 1.0,
color: Colors.black,
alignment: Alignment.bottomLeft,
);
}
}
识别身份证时的加载框(getProgressDialog())代码如下:
Widget getProgressDialog(){
if(showProgressDialog){
return new Container(
color: Colors.black12,
alignment: Alignment.center,
child: SpinKitFadingCircle(color: Colors.blueAccent),
);
}else{
return new Container(
height: 1.0,
width: 1.0,
color: Colors.black,
alignment: Alignment.bottomLeft,
);
}
}
加载框用的是flutter的官方插件:flutter_spinkit(https://pub.dev/packages/flutter_spinkit),可自由选择加载框样式。
身份证识别失败的提示信息(getRestartAlert())代码如下:
Widget getRestartAlert(){
if(restart){
return new Container(
color: Colors.white,
alignment: Alignment.center,
child: Column(
children: <Widget>[
new Text(
"身份证识别失败,重新采集识别?",
style: TextStyle(color: Colors.black26,fontSize: 18.0),
),
IconButton(
icon: Icon(
Icons.subdirectory_arrow_right,
color: Colors.blueAccent,
),
onPressed:toRestartIdentify() ,),
]
),
);
}else{
return new Container(
height: 1.0,
width: 1.0,
color: Colors.black,
alignment: Alignment.bottomLeft,
);
}
}
4.相机拍照
异步获取相机
List<CameraDescription> cameras;
Future<void> getCameras() async {
// Fetch the available cameras before initializing the app.
try {
cameras = await availableCameras();
//FlutterImageCompress.showNativeLog = true;
} on CameraException catch (e) {
print(e.toString());
}
}
相机获取成功后,初始化CameraController,可选择摄像头(成功后才可展示相机布局,否则相机无法正常工作)
@override
void initState() {
super.initState();
if(cameras != null && !cameras.isEmpty){
onNewCameraSelected(cameras[0]);// 后置摄像头
// onNewCameraSelected(cameras[1]);// 前置摄像头
}
}
void onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
}
controller = CameraController(cameraDescription, ResolutionPreset.high);
// If the controller is updated then update the UI.
controller.addListener(() {
if (mounted) setState(() {});
if (controller.value.hasError) {
showInSnackBar('Camera error ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
拍照按钮点击事件实现
void onTakePictureButtonPressed() {
takePicture().then((String filePath) {
if (mounted) {
setState(() {
videoController = null;
videoController?.dispose();
});
if (filePath != null) {
//showInSnackBar('Picture saved to $filePath');
photoPath = filePath;
setState(() { });// 通过设置photoPath 的状态展示图片预览页
getIDCardInfo(File(filePath));//进行身份证识别
}
}
});
}
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
showInSnackBar('Error: select a camera first.');
return null;
}
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/Pictures/flutter_test';
await Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
if (controller.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
await controller.takePicture(filePath);
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
return filePath;
}
识别身份证信息
Future getIDCardInfo(File file) async {
showProgressDialog =true;//展示加载框
Dio dio = new Dio();
dio.options.contentType=ContentType.parse("multipart/form-data");
var baseUrl = "https://api-cn.faceplusplus.com/cardpp/v1/ocridcard";
final String targetPath = await getTempDir();
File compressFile =await getCompressImage(file, targetPath);
FormData formData = new FormData.from({
"api_key": APPApiKey.Face_api_key,
"api_secret": APPApiKey.Face_api_secret,
"image_file": new UploadFileInfo(compressFile, "image_file")
});
Response<Map> response;
try {
response =await dio.post<Map>(baseUrl,data:formData);
} catch (e) {
print(e);
showProgressDialog =false;//隐藏加载框
setState(() {});
}
showProgressDialog =false;//隐藏加载框
setState(() {});
if(response != null){
print(response.data);
Map<String,Object> resultMap = response.data;
List<dynamic> cards =resultMap['cards'];
if(cards != null && !cards.isEmpty){
Map<String,dynamic> card = cards[0];
Navigator.pop(context, card);
var idcard = card[IDCardIdentifyResult.id_card_number];
var name = card[IDCardIdentifyResult.name];
var birth = card[IDCardIdentifyResult.birthday];
var address = card[IDCardIdentifyResult.address];
print('身份证号: ${idcard}');
print('姓名: ${name}');
print('出生日期: ${birth}');
print('地址: ${address}');
}else{
restart = true;//展示重新拍照的提示
setState(() {});
}
}else{
restart = true;//展示重新拍照的提示
setState(() {});
}
}
end