flutter學(xué)習(xí)筆記:自定義相機(jī)

以身份證識(shí)別(Face++的API接口實(shí)現(xiàn)方式:https://console.faceplusplus.com.cn/documents/5671702)為例苟弛,實(shí)現(xiàn)一個(gè)自定義相機(jī)的功能魄咕,實(shí)現(xiàn)效果:自定義相機(jī)頁(yè)面、拍照、照片預(yù)覽。

自定義相機(jī)頁(yè)面效果展示

自定義相機(jī)頁(yè)面效果展示

代碼實(shí)現(xiàn)

GitHub地址(https://github.com/Lightforest/FlutterVideo)

1.在pubspec.yaml中添加插件:

camera(flutter官方插件:https://pub.dev/packages/camera

2.圖片資源準(zhǔn)備

將身份證框圖放在項(xiàng)目根目錄下的assets目錄中備用,并在pubspec.yaml中導(dǎo)入目錄


身份證框圖放置路徑

圖片路徑導(dǎo)入

3.頁(yè)面布局實(shí)現(xiàn)

頁(yè)面總布局代碼如下:


@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用來(lái)設(shè)置當(dāng)前可用空間的占優(yōu)比

              child:  new Stack(children: <Widget>[

                _cameraPreviewWidget(),//相機(jī)視圖

                _cameraFloatImage(),//懸浮的身份證框圖

              ]),

            ),

            Expanded(

              flex: 1,//flex用來(lái)設(shè)置當(dāng)前可用空間的占優(yōu)比

              child: _takePictureLayout(),//拍照操作區(qū)域布局

            ),

          ],),

          getPhotoPreview(),//圖片預(yù)覽布局

          getProgressDialog(),//數(shù)據(jù)加載中提示框

          getRestartAlert(),// 身份證識(shí)別失敗,重新拍照的提示按鈕

        ]),

      )

  );

}

頁(yè)面總布局共包括6部分的內(nèi)容:相機(jī)預(yù)覽、身份證框圖陶贼、拍照按鈕區(qū)域、拍照后的圖片預(yù)覽待秃、識(shí)別身份證時(shí)的加載框拜秧、身份證識(shí)別失敗的提示信息。

自定義相機(jī)頁(yè)面布局說(shuō)明圖.png
相機(jī)預(yù)覽(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'),
        ));
  }
拍照區(qū)域(_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,
            ),
          ),
        ));
  }
拍照后的圖片預(yù)覽(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,
    );
  }
}
識(shí)別身份證時(shí)的加載框(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)章郁,可自由選擇加載框樣式枉氮。

身份證識(shí)別失敗的提示信息(getRestartAlert())代碼如下:
Widget getRestartAlert(){
    if(restart){
      return new Container(
        color: Colors.white,
        alignment: Alignment.center,
        child: Column(
            children: <Widget>[
              new Text(
                "身份證識(shí)別失敗,重新采集識(shí)別?",
                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.相機(jī)拍照

異步獲取相機(jī)

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());
    }
  }

相機(jī)獲取成功后,初始化CameraController嘲恍,可選擇攝像頭(成功后才可展示相機(jī)布局足画,否則相機(jī)無(wú)法正常工作)

@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(() {});
    }
  }

拍照按鈕點(diǎn)擊事件實(shí)現(xiàn)

void onTakePictureButtonPressed() {
    takePicture().then((String filePath) {
      if (mounted) {
        setState(() {
          videoController = null;
          videoController?.dispose();
        });
        if (filePath != null) {
          //showInSnackBar('Picture saved to $filePath');
          photoPath = filePath;
          setState(() { });// 通過(guò)設(shè)置photoPath 的狀態(tài)展示圖片預(yù)覽頁(yè)
          getIDCardInfo(File(filePath));//進(jìn)行身份證識(shí)別
        }
      }
    });
  }

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;
  }

識(shí)別身份證信息

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('身份證號(hào): ${idcard}');
        print('姓名: ${name}');
        print('出生日期: ${birth}');
        print('地址: ${address}');
      }else{
        restart = true;//展示重新拍照的提示
        setState(() {});
      }
    }else{
      restart = true;//展示重新拍照的提示
      setState(() {});
    }
  }

end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市佃牛,隨后出現(xiàn)的幾起案子淹辞,更是在濱河造成了極大的恐慌,老刑警劉巖俘侠,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件象缀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡爷速,警方通過(guò)查閱死者的電腦和手機(jī)央星,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惫东,“玉大人莉给,你說(shuō)我怎么就攤上這事×冢” “怎么了颓遏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)滞时。 經(jīng)常有香客問(wèn)我叁幢,道長(zhǎng),這世上最難降的妖魔是什么坪稽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任曼玩,我火速辦了婚禮,結(jié)果婚禮上窒百,老公的妹妹穿的比我還像新娘黍判。我一直安慰自己,他們只是感情好贝咙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布样悟。 她就那樣靜靜地躺著,像睡著了一般庭猩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陈症,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天蔼水,我揣著相機(jī)與錄音,去河邊找鬼录肯。 笑死趴腋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播优炬,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颁井,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蠢护?” 一聲冷哼從身側(cè)響起雅宾,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葵硕,沒(méi)想到半個(gè)月后眉抬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體患民,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芜飘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侧但。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片介评。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡库北,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出们陆,到底是詐尸還是另有隱情寒瓦,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布棒掠,位于F島的核電站孵构,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烟很。R本人自食惡果不足惜颈墅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雾袱。 院中可真熱鬧恤筛,春花似錦、人聲如沸芹橡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)林说。三九已至煎殷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腿箩,已是汗流浹背豪直。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留珠移,地道東北人弓乙。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓末融,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親暇韧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勾习,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容