Thursday, August 9, 2018

Flutter - Capture Image from camera or gallery and apply crop.

In modern application, if you implementing user profile feature. So, you should implement user profile pic and allow a user to set and change profile pic. A user can set a profile pic from his/her mobile gallery or capture image from camera. 
Image picker in flutter
Besides just picking images from gallery and camera. You can crop it with desired aspect ratio. It is very important if you noticed every app like Facebook, Google etc asks you to crop the image into a square because it fits the best for different use cases.

In this post, we’ll develop an application that picks an image from camera or gallery. After that, we crop it for fit in a circular image widget.

In this project user will have following choices:
  • Capture profile picture from the camera.
  • Choose profile picture from gallery.
  • Cancel.
The user will need to choose one option from the above three options and then depending on the option chosen by the user. We will either capture an image from the camera or open the gallery.



We have created a Flutter plugin for this post. You can use it instantly. version:1.1.2

flutter-image-picker-plugin
>  
let's start its implementation with the following steps.

Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. For pick image from gallery and camera, we have used image_picker: "^0.4.5" and for crop selected we have used image_cropper: ^0.0.4. As you can see, we have declared it in pubspec.yaml file.

Image crop and picker dart lib

3. Open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/home_screen.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Image picker', theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF167F67), ), home: new HomeScreen(title: 'Flutter Image picker'), ); } }
4. After that create HomeScreen(home_screen.dartwidget. It is root widget of our project. Here, we have implemented two interfaces TickerProviderStateMixin and ImagePickerListener.  We using TickerProviderStateMixin for handle images from local storage and ImagePickerListener provide a callback method from image picker handler class. We have created an instance of animation controller. It will control bottom to up slide animation.
home_screen.dart
@override void initState() { super.initState(); _controller = new AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); imagePicker=new ImagePickerHandler(this,_controller); imagePicker.init(); }


5. Now modify build method of home_screen.dart. Here, we have used stack widget for display empty image view.

Empty image picker box
home_screen.dart
new Stack( children: <Widget>[ new Center( child: new CircleAvatar( radius: 80.0, backgroundColor: const Color(0xFF778899), ), ), new Center( child: new Image.asset("assets/photo_camera.png"), ), ], ) }
for display selected and cropped image, we have used another widget group.
Selected image picker box
home_screen.dart
new Container( height: 160.0, width: 160.0, decoration: new BoxDecoration( color: const Color(0xff7c94b6), image: new DecorationImage( image: new ExactAssetImage(_image.path), fit: BoxFit.cover, ), border: Border.all(color: Colors.red, width: 5.0), borderRadius: new BorderRadius.all(const Radius.circular(80.0)), ), ),
after that merge all code snippet and you will see final home_screen.dart look like below.
home_screen.dart
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_handler.dart'; import 'package:flutter_image_ppicker/image_picker_dialog.dart'; class HomeScreen extends StatefulWidget { HomeScreen({Key key, this.title}) : super(key: key); final String title; @override _HomeScreenState createState() => new _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin,ImagePickerListener{ File _image; AnimationController _controller; ImagePickerHandler imagePicker; @override void initState() { super.initState(); _controller = new AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); imagePicker=new ImagePickerHandler(this,_controller); imagePicker.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title, style: new TextStyle( color: Colors.white ), ), ), body: new GestureDetector( onTap: () => imagePicker.showDialog(context), child: new Center( child: _image == null ? new Stack( children: <Widget>[ new Center( child: new CircleAvatar( radius: 80.0, backgroundColor: const Color(0xFF778899), ), ), new Center( child: new Image.asset("assets/photo_camera.png"), ), ], ) : new Container( height: 160.0, width: 160.0, decoration: new BoxDecoration( color: const Color(0xff7c94b6), image: new DecorationImage( image: new ExactAssetImage(_image.path), fit: BoxFit.cover, ), border: Border.all(color: Colors.red, width: 5.0), borderRadius: new BorderRadius.all(const Radius.circular(80.0)), ), ), ), ), ); } @override userImage(File _image) { setState(() { this._image = _image; }); } }


6. Now create image_picker_handler.dart. It will handle user interface of image picker widget or dialog. Here, we have created openCarmra and openGallery methods to get an image. In this file, we have created cropImage method.  As you can see.


Image crop
image_picker_handler.dart
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_dialog.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; class ImagePickerHandler { ImagePickerDialog imagePicker; AnimationController _controller; ImagePickerListener _listener; ImagePickerHandler(this._listener, this._controller); openCamera() async { imagePicker.dismissDialog(); var image = await ImagePicker.pickImage(source: ImageSource.camera); cropImage(image); } openGallery() async { imagePicker.dismissDialog(); var image = await ImagePicker.pickImage(source: ImageSource.gallery); cropImage(image); } void init() { imagePicker = new ImagePickerDialog(this, _controller); imagePicker.initState(); } Future cropImage(File image) async { File croppedFile = await ImageCropper.cropImage( sourcePath: image.path, ratioX: 1.0, ratioY: 1.0, maxWidth: 512, maxHeight: 512, ); _listener.userImage(croppedFile); } showDialog(BuildContext context) { imagePicker.getImage(context); } } abstract class ImagePickerListener { userImage(File _image); }
if you want to use this class. Then you have to call init method to create an instance of image picker dialog. After that call showDialog method for show image picker. 

7. At the end, create a final class of project that is image_picker_dialog.dart. Here, we have defined getImage method that we calling from image_picker_halder.dart. It will show a dialog with a bottom to up slide animation. As you can see above video.
image_picker_dialog.dart
getImage(BuildContext context) { if (_controller == null || _drawerDetailsPosition == null || _drawerContentsOpacity == null) { return; } _controller.forward(); showDialog( context: context, builder: (BuildContext context) => new SlideTransition( position: _drawerDetailsPosition, child: new FadeTransition( opacity: new ReverseAnimation(_drawerContentsOpacity), child: this, ), ), ); }

 
in this file, we have created a reusable method roundedButton. By using this method, we have created the following image picker and a cancel button on the dialog.


Image picker options

image_picker_dialog.dart
Widget roundedButton( String buttonLabel, EdgeInsets margin, Color bgColor, Color textColor) { var loginBtn = new Container( margin: margin, padding: EdgeInsets.all(15.0), alignment: FractionalOffset.center, decoration: new BoxDecoration( color: bgColor, borderRadius: new BorderRadius.all(const Radius.circular(100.0)), boxShadow: <BoxShadow>[ BoxShadow( color: const Color(0xFF696969), offset: Offset(1.0, 6.0), blurRadius: 0.001, ), ], ), child: Text( buttonLabel, style: new TextStyle( color: textColor, fontSize: 20.0, fontWeight: FontWeight.bold), ), ); return loginBtn; }
as you can see a final class of image_picker_dialog.dart. We have called above method in the build method. Here, we have final class.
image_picker_dialog.dart
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_handler.dart'; class ImagePickerDialog extends StatelessWidget { ImagePickerHandler _listener; AnimationController _controller; BuildContext context; ImagePickerDialog(this._listener, this._controller); Animation<double> _drawerContentsOpacity; Animation<Offset> _drawerDetailsPosition; void initState() { _drawerContentsOpacity = new CurvedAnimation( parent: new ReverseAnimation(_controller), curve: Curves.fastOutSlowIn, ); _drawerDetailsPosition = new Tween<Offset>( begin: const Offset(0.0, 1.0), end: Offset.zero, ).animate(new CurvedAnimation( parent: _controller, curve: Curves.fastOutSlowIn, )); } getImage(BuildContext context) { if (_controller == null || _drawerDetailsPosition == null || _drawerContentsOpacity == null) { return; } _controller.forward(); showDialog( context: context, builder: (BuildContext context) => new SlideTransition( position: _drawerDetailsPosition, child: new FadeTransition( opacity: new ReverseAnimation(_drawerContentsOpacity), child: this, ), ), ); } void dispose() { _controller.dispose(); } startTime() async { var _duration = new Duration(milliseconds: 200); return new Timer(_duration, navigationPage); } void navigationPage() { Navigator.pop(context); } dismissDialog() { _controller.reverse(); startTime(); } @override Widget build(BuildContext context) { this.context = context; return new Material( type: MaterialType.transparency, child: new Opacity( opacity: 1.0, child: new Container( padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 20.0), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new GestureDetector( onTap: () => _listener.openCamera(), child: roundedButton( "Camera", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), new GestureDetector( onTap: () => _listener.openGallery(), child: roundedButton( "Gallery", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), const SizedBox(height: 15.0), new GestureDetector( onTap: () => dismissDialog(), child: new Padding( padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0), child: roundedButton( "Cancel", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), ), ], ), ), )); } Widget roundedButton( String buttonLabel, EdgeInsets margin, Color bgColor, Color textColor) { var loginBtn = new Container( margin: margin, padding: EdgeInsets.all(15.0), alignment: FractionalOffset.center, decoration: new BoxDecoration( color: bgColor, borderRadius: new BorderRadius.all(const Radius.circular(100.0)), boxShadow: <BoxShadow>[ BoxShadow( color: const Color(0xFF696969), offset: Offset(1.0, 6.0), blurRadius: 0.001, ), ], ), child: Text( buttonLabel, style: new TextStyle( color: textColor, fontSize: 20.0, fontWeight: FontWeight.bold), ), ); return loginBtn; } }

If you have followed the article carefully, you can see the app running very smoothly as shown in the video demo. But if you facing any problem.


Source Code               Flutter firebse storage apk

You can get the working project from Github and please feel free to ask in the comment section below. 


Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...