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
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.
- dependencies:
- flutter:
- sdk: flutter
- cupertino_icons: ^0.1.2
- camera: ^0.5.4+1
- 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.
- import 'dart:async';
- import 'dart:io';
- import 'package:camera/camera.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:flutter/material.dart';
-
- class CameraPage extends StatefulWidget {
- final List<CameraDescription> cameras;
- CameraPage(this.cameras);
- @override
- _CameraPageState createState() => _CameraPageState();
- }
-
- class _CameraPageState extends State<CameraPage> {
- String imagePath;
- bool _toggleCamera = false;
- CameraController controller;
-
- @override
- void initState() {
- onCameraSelected(widget.cameras[0]);
- super.initState();
- }
-
- @override
- void dispose() {
- controller?.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- if (widget.cameras.isEmpty) {
- return Container(
- alignment: Alignment.center,
- padding: EdgeInsets.all(16.0),
- child: Text(
- 'No Camera Found',
- style: TextStyle(
- fontSize: 16.0,
- color: Colors.white,
- ),
- ),
- );
- }
-
- if (!controller.value.isInitialized) {
- return Container();
- }
-
- return AspectRatio(
- aspectRatio: controller.value.aspectRatio,
- child: Container(
- child: Stack(
- children: <Widget>[
- CameraPreview(controller),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- width: double.infinity,
- height: 120.0,
- padding: EdgeInsets.all(20.0),
- color: Color.fromRGBO(00, 00, 00, 0.7),
- child: Stack(
- children: <Widget>[
- Align(
- alignment: Alignment.center,
- child: Material(
- color: Colors.transparent,
- child: InkWell(
- borderRadius: BorderRadius.all(Radius.circular(50.0)),
- onTap: () {
- _captureImage();
- },
- child: Container(
- padding: EdgeInsets.all(4.0),
- child: Image.asset(
- 'assets/images/shutter_1.png',
- width: 72.0,
- height: 72.0,
- ),
- ),
- ),
- ),
- ),
- Align(
- alignment: Alignment.centerRight,
- child: Material(
- color: Colors.transparent,
- child: InkWell(
- borderRadius: BorderRadius.all(Radius.circular(50.0)),
- onTap: () {
- if (!_toggleCamera) {
- onCameraSelected(widget.cameras[1]);
- setState(() {
- _toggleCamera = true;
- });
- } else {
- onCameraSelected(widget.cameras[0]);
- setState(() {
- _toggleCamera = false;
- });
- }
- },
- child: Container(
- padding: EdgeInsets.all(4.0),
- child: Image.asset(
- 'assets/images/switch_camera_3.png',
- color: Colors.grey[200],
- width: 42.0,
- height: 42.0,
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- );
- }
-
- void onCameraSelected(CameraDescription cameraDescription) async {
- if (controller != null) await controller.dispose();
- controller = CameraController(cameraDescription, ResolutionPreset.medium);
-
- controller.addListener(() {
- if (mounted) setState(() {});
- if (controller.value.hasError) {
- showMessage('Camera Error: ${controller.value.errorDescription}');
- }
- });
-
- try {
- await controller.initialize();
- } on CameraException catch (e) {
- showException(e);
- }
-
- if (mounted) setState(() {});
- }
-
- String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
-
- void _captureImage() {
- takePicture().then((String filePath) {
- if (mounted) {
- setState(() {
- imagePath = filePath;
- });
- if (filePath != null) {
- showMessage('Picture saved to $filePath');
- setCameraResult();
- }
- }
- });
- }
-
- void setCameraResult() {
- Navigator.pop(context, imagePath);
- }
-
- Future<String> takePicture() async {
- if (!controller.value.isInitialized) {
- showMessage('Error: select a camera first.');
- return null;
- }
- final Directory extDir = await getApplicationDocumentsDirectory();
- final String dirPath = '${extDir.path}/Images';
- await new Directory(dirPath).create(recursive: true);
- final String filePath = '$dirPath/${timestamp()}.jpg';
-
- if (controller.value.isTakingPicture) {
-
- return null;
- }
-
- try {
- await controller.takePicture(filePath);
- } on CameraException catch (e) {
- showException(e);
- return null;
- }
- return filePath;
- }
-
- void showException(CameraException e) {
- logError(e.code, e.description);
- showMessage('Error: ${e.code}\n${e.description}');
- }
-
- void showMessage(String message) {
- print(message);
- }
-
- void logError(String code, String message) =>
- print('Error: $code\nMessage: $message');
- }
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.
- import 'dart:async';
- import 'dart:io';
- import 'package:camera/camera.dart';
- import 'package:flutter_camera_overlay/camera_page.dart';
- import 'package:flutter/material.dart';
-
- void main() => runApp(MyApp());
-
- class MyApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- theme: ThemeData(
- primarySwatch: Colors.green,
- ),
- home: HomePage(),
- );
- }
- }
-
- class HomePage extends StatefulWidget {
- @override
- _HomePageState createState() => _HomePageState();
- }
-
- class _HomePageState extends State<HomePage> {
- String _imagePath;
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('Flutter Camera Overlay'),
- ),
- body: Stack(
- children: <Widget>[
- _imagePath != null
- ? capturedImageWidget(_imagePath)
- : noImageWidget(),
- fabWidget(),
- ],
- ),
- );
- }
-
- Widget noImageWidget() {
- return SizedBox.expand(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: <Widget>[
- Container(
- child: Icon(
- Icons.image,
- color: Colors.grey,
- ),
- width: 60.0,
- height: 60.0,
- ),
- Container(
- margin: EdgeInsets.only(top: 8.0),
- child: Text(
- 'No Image Captured',
- style: TextStyle(
- color: Colors.grey,
- fontSize: 16.0,
- ),
- ),
- ),
- ],
- ));
- }
-
- Widget capturedImageWidget(String imagePath) {
- return SizedBox.expand(
- child: Image.file(File(
- imagePath,
- )),
- );
- }
-
- Widget fabWidget() {
- return Positioned(
- bottom: 30.0,
- right: 16.0,
- child: FloatingActionButton(
- onPressed: openCamera,
- child: Icon(
- Icons.photo_camera,
- color: Colors.white,
- ),
- backgroundColor: Colors.green,
- ),
- );
- }
-
- Future openCamera() async {
- availableCameras().then((cameras) async {
- final imagePath = await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => CameraPage(cameras),
- ),
- );
- setState(() {
- _imagePath = imagePath;
- });
- });
- }
- }
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.