Flutter Camera Overlay Or Overlap Using Stack Bar

Introduction

 
In this article, we will learn how to implement the camera overlay in Flutter using Stack Bar widget. Using Stack Bar widget, we can overlap the widgets and position using top, left, right, and bottom side.
 
In this article, we will cover topics like how to add a camera plugin in Flutter, how to open the camera in Flutter, how to capture an image, and how to overlap widgets over one another. So, let’s begin to implement Camera Overlay in Flutter.
 
Output
 
Flutter Camera Overlay Or Overlap Using Stack BarFlutter Camera Overlay Or Overlap Using Stack Bar
 
Plugins Required
 
camera: ^0.5.4+1
path_provider: ^0.4.1
 

Steps

 
Step 1
 
The first and most basic step is to create a new application in Flutter. If you are a beginner in Flutter, you can check my blog Create a first app in Flutter. I have created an app named as “flutter_camera_overlay”.
 
Step 2
 
Now, add dependency in pubspec yaml file to add the camera and path provider plugin.
  1. dependencies:  
  2.  flutter:  
  3.    sdk: flutter  
  4.  cupertino_icons: ^0.1.2  
  5.  camera: ^0.5.4+1  
  6.  path_provider: ^0.4.1  
Step 3
 
Now, we will create a camera_page.dart file which will implement the camera app which is overlapped by image capture and camera shutter toggle button. To overlap widgets, we use the Stack Bar widget. We will return the captured image to the home page to show. Following is the programming representation of camera operations.
  1. import 'dart:async';  
  2. import 'dart:io';  
  3. import 'package:camera/camera.dart';  
  4. import 'package:path_provider/path_provider.dart';  
  5. import 'package:flutter/material.dart';  
  6.    
  7. class CameraPage extends StatefulWidget {  
  8.  final List<CameraDescription> cameras;  
  9.  CameraPage(this.cameras);  
  10.  @override  
  11.  _CameraPageState createState() => _CameraPageState();  
  12. }  
  13.    
  14. class _CameraPageState extends State<CameraPage> {  
  15.  String imagePath;  
  16.  bool _toggleCamera = false;  
  17.  CameraController controller;  
  18.    
  19.  @override  
  20.  void initState() {  
  21.    onCameraSelected(widget.cameras[0]);  
  22.    super.initState();  
  23.  }  
  24.    
  25.  @override  
  26.  void dispose() {  
  27.    controller?.dispose();  
  28.    super.dispose();  
  29.  }  
  30.    
  31.  @override  
  32.  Widget build(BuildContext context) {  
  33.    if (widget.cameras.isEmpty) {  
  34.      return Container(  
  35.        alignment: Alignment.center,  
  36.        padding: EdgeInsets.all(16.0),  
  37.        child: Text(  
  38.          'No Camera Found',  
  39.          style: TextStyle(  
  40.            fontSize: 16.0,  
  41.            color: Colors.white,  
  42.          ),  
  43.        ),  
  44.      );  
  45.    }  
  46.    
  47.    if (!controller.value.isInitialized) {  
  48.      return Container();  
  49.    }  
  50.    
  51.    return AspectRatio(  
  52.      aspectRatio: controller.value.aspectRatio,  
  53.      child: Container(  
  54.        child: Stack(  
  55.          children: <Widget>[  
  56.            CameraPreview(controller),  
  57.            Align(  
  58.              alignment: Alignment.bottomCenter,  
  59.              child: Container(  
  60.                width: double.infinity,  
  61.                height: 120.0,  
  62.                padding: EdgeInsets.all(20.0),  
  63.                color: Color.fromRGBO(00, 00, 00, 0.7),  
  64.                child: Stack(  
  65.                  children: <Widget>[  
  66.                    Align(  
  67.                      alignment: Alignment.center,  
  68.                      child: Material(  
  69.                        color: Colors.transparent,  
  70.                        child: InkWell(  
  71.                          borderRadius: BorderRadius.all(Radius.circular(50.0)),  
  72.                          onTap: () {  
  73.                            _captureImage();  
  74.                          },  
  75.                          child: Container(  
  76.                            padding: EdgeInsets.all(4.0),  
  77.                            child: Image.asset(  
  78.                              'assets/images/shutter_1.png',  
  79.                              width: 72.0,  
  80.                              height: 72.0,  
  81.                            ),  
  82.                          ),  
  83.                        ),  
  84.                      ),  
  85.                    ),  
  86.                    Align(  
  87.                      alignment: Alignment.centerRight,  
  88.                      child: Material(  
  89.                        color: Colors.transparent,  
  90.                        child: InkWell(  
  91.                          borderRadius: BorderRadius.all(Radius.circular(50.0)),  
  92.                          onTap: () {  
  93.                            if (!_toggleCamera) {  
  94.                              onCameraSelected(widget.cameras[1]);  
  95.                              setState(() {  
  96.                                _toggleCamera = true;  
  97.                              });  
  98.                            } else {  
  99.                              onCameraSelected(widget.cameras[0]);  
  100.                              setState(() {  
  101.                                _toggleCamera = false;  
  102.                              });  
  103.                            }  
  104.                          },  
  105.                          child: Container(  
  106.                            padding: EdgeInsets.all(4.0),  
  107.                            child: Image.asset(  
  108.                              'assets/images/switch_camera_3.png',  
  109.                              color: Colors.grey[200],  
  110.                              width: 42.0,  
  111.                              height: 42.0,  
  112.                            ),  
  113.                          ),  
  114.                        ),  
  115.                      ),  
  116.                    ),  
  117.                  ],  
  118.                ),  
  119.              ),  
  120.            ),  
  121.          ],  
  122.        ),  
  123.      ),  
  124.    );  
  125.  }  
  126.    
  127.  void onCameraSelected(CameraDescription cameraDescription) async {  
  128.    if (controller != null) await controller.dispose();  
  129.    controller = CameraController(cameraDescription, ResolutionPreset.medium);  
  130.    
  131.    controller.addListener(() {  
  132.      if (mounted) setState(() {});  
  133.      if (controller.value.hasError) {  
  134.        showMessage('Camera Error: ${controller.value.errorDescription}');  
  135.      }  
  136.    });  
  137.    
  138.    try {  
  139.      await controller.initialize();  
  140.    } on CameraException catch (e) {  
  141.      showException(e);  
  142.    }  
  143.    
  144.    if (mounted) setState(() {});  
  145.  }  
  146.    
  147.  String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();  
  148.    
  149.  void _captureImage() {  
  150.    takePicture().then((String filePath) {  
  151.      if (mounted) {  
  152.        setState(() {  
  153.          imagePath = filePath;  
  154.        });  
  155.        if (filePath != null) {  
  156.          showMessage('Picture saved to $filePath');  
  157.          setCameraResult();  
  158.        }  
  159.      }  
  160.    });  
  161.  }  
  162.    
  163.  void setCameraResult() {  
  164.    Navigator.pop(context, imagePath);  
  165.  }  
  166.    
  167.  Future<String> takePicture() async {  
  168.    if (!controller.value.isInitialized) {  
  169.      showMessage('Error: select a camera first.');  
  170.      return null;  
  171.    }  
  172.    final Directory extDir = await getApplicationDocumentsDirectory();  
  173.    final String dirPath = '${extDir.path}/Images';  
  174.    await new Directory(dirPath).create(recursive: true);  
  175.    final String filePath = '$dirPath/${timestamp()}.jpg';  
  176.    
  177.    if (controller.value.isTakingPicture) {  
  178.      // A capture is already pending, do nothing.  
  179.      return null;  
  180.    }  
  181.    
  182.    try {  
  183.      await controller.takePicture(filePath);  
  184.    } on CameraException catch (e) {  
  185.      showException(e);  
  186.      return null;  
  187.    }  
  188.    return filePath;  
  189.  }  
  190.    
  191.  void showException(CameraException e) {  
  192.    logError(e.code, e.description);  
  193.    showMessage('Error: ${e.code}\n${e.description}');  
  194.  }  
  195.    
  196.  void showMessage(String message) {  
  197.    print(message);  
  198.  }  
  199.    
  200.  void logError(String code, String message) =>  
  201.      print('Error: $code\nMessage: $message');  
  202. }  
Step 4
 
Now, we will create a homepage in main.dart file to open the camera and show captured image in this file. We will also use Stack Bar to overlap the fab button and image placeholder. Following is the programming implementation of that. 
  1. import 'dart:async';  
  2. import 'dart:io';  
  3. import 'package:camera/camera.dart';  
  4. import 'package:flutter_camera_overlay/camera_page.dart';  
  5. import 'package:flutter/material.dart';  
  6.    
  7. void main() => runApp(MyApp());  
  8.    
  9. class MyApp extends StatelessWidget {  
  10.  @override  
  11.  Widget build(BuildContext context) {  
  12.    return MaterialApp(  
  13.      theme: ThemeData(  
  14.        primarySwatch: Colors.green,  
  15.      ),  
  16.      home: HomePage(),  
  17.    );  
  18.  }  
  19. }  
  20.    
  21. class HomePage extends StatefulWidget {  
  22.  @override  
  23.  _HomePageState createState() => _HomePageState();  
  24. }  
  25.    
  26. class _HomePageState extends State<HomePage> {  
  27.  String _imagePath;  
  28.  @override  
  29.  Widget build(BuildContext context) {  
  30.    return Scaffold(  
  31.      appBar: AppBar(  
  32.        title: Text('Flutter Camera Overlay'),  
  33.      ),  
  34.      body: Stack(  
  35.        children: <Widget>[  
  36.          _imagePath != null  
  37.              ? capturedImageWidget(_imagePath)  
  38.              : noImageWidget(),  
  39.          fabWidget(),  
  40.        ],  
  41.      ),  
  42.    );  
  43.  }  
  44.    
  45.  Widget noImageWidget() {  
  46.    return SizedBox.expand(  
  47.        child: Column(  
  48.      mainAxisAlignment: MainAxisAlignment.center,  
  49.      children: <Widget>[  
  50.        Container(  
  51.          child: Icon(  
  52.            Icons.image,  
  53.            color: Colors.grey,  
  54.          ),  
  55.          width: 60.0,  
  56.          height: 60.0,  
  57.        ),  
  58.        Container(  
  59.          margin: EdgeInsets.only(top: 8.0),  
  60.          child: Text(  
  61.            'No Image Captured',  
  62.            style: TextStyle(  
  63.              color: Colors.grey,  
  64.              fontSize: 16.0,  
  65.            ),  
  66.          ),  
  67.        ),  
  68.      ],  
  69.    ));  
  70.  }  
  71.    
  72.  Widget capturedImageWidget(String imagePath) {  
  73.    return SizedBox.expand(  
  74.      child: Image.file(File(  
  75.        imagePath,  
  76.      )),  
  77.    );  
  78.  }  
  79.    
  80.  Widget fabWidget() {  
  81.    return Positioned(  
  82.      bottom: 30.0,  
  83.      right: 16.0,  
  84.      child: FloatingActionButton(  
  85.        onPressed: openCamera,  
  86.        child: Icon(  
  87.          Icons.photo_camera,  
  88.          color: Colors.white,  
  89.        ),  
  90.        backgroundColor: Colors.green,  
  91.      ),  
  92.    );  
  93.  }  
  94.    
  95.  Future openCamera() async {  
  96.    availableCameras().then((cameras) async {  
  97.      final imagePath = await Navigator.push(  
  98.        context,  
  99.        MaterialPageRoute(  
  100.          builder: (context) => CameraPage(cameras),  
  101.        ),  
  102.      );  
  103.      setState(() {  
  104.        _imagePath = imagePath;  
  105.      });  
  106.    });  
  107.  }  
  108. }  
Hooray…. Run the app and Test It on emulator/simulator or device :)))
 
NOTE
If you find errors like "uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library", then change the minSdkVersion to 21 in android/app/build.gradle and run the app.
 

Conclusion

 
We have learned how to set camera overlay in Flutter using Stack Bar. We have also learned how to integrate a camera plugin and capture an image in Flutter.


Similar Articles