flutter学习笔记:自定义相机

以身份证识别(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部分的内容:相机预览、身份证框图、拍照按钮区域、拍照后的图片预览、识别身份证时的加载框、身份证识别失败的提示信息。

自定义相机页面布局说明图.png
相机预览(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

推荐阅读更多精彩内容