👌 Awesome built-in UI

CamerAwesome comes with a full built UI that you can use as is.

Use CameraAwesomeBuilder.awesome() to get a complete ready-to-use camera experience within your app.

Here is a concrete example using better_open_file to display the last media captured:

CameraAwesomeBuilder.awesome(
  saveConfig: SaveConfig.photoAndVideo(),
  onMediaTap: (mediaCapture) {
    OpenFile.open(mediaCapture.filePath);
  },
)

Base Awesome UI

📁 Camera captures configuration#

CameraAwesomeBuilder requires a SaveConfig parameter. You can create one with one of the following factories:

  • SaveConfig.photo() if you only want to take photos
  • SaveConfig.video() to only take videos
  • SaveConfig.photoAndVideo() if you want to switch between photo and video modes.

These factories don't require additional parameters, but you might want to customize a few things, like where to save your files.

Here is a complete example to overwrite the default behaviors:

SaveConfig.photoAndVideo(
  // 1.
  initialCaptureMode: CaptureMode.photo,
  // 2.
  photoPathBuilder: (sensors) async {
    ...
  },
  // 3.
  videoPathBuilder: (sensors) async {
    ...
  },
  // 4.
  videoOptions: VideoOptions(
    enableAudio: true,
    ios: CupertinoVideoOptions(
      fps: 10,
      // TODODOC Add other possble params
    ),
    android: AndroidVideoOptions(
      bitrate: 6000000,
      quality: VideoRecordingQuality.fhd,
      fallbackStrategy: QualityFallbackStrategy.lower,
    ),
  ),
  // 5.
  exifPreferences: ExifPreferences(saveGPSLocation: true),
  // 6.
  mirrorFrontCamera: true,
)

Let's break it down:

  1. When using photoAndVideo mode, you can choose which mode to start with. Here we start with photo mode (default).
  2. You can customize the path where your photos will be saved.
  3. You can also customize where to save your videos.
  4. The video recording can be customized using VideoOptions. Note that each platform has its own settings.
  5. You can also enable or disable the GPS location in the EXIF data of your photos.
  6. Set if you want the front camera pictures & videos to be mirrored like in the preview.

A photoPathBuilder could look like this:

SaveConfig.photoAndVideo(
  photoPathBuilder: (sensors) async {
    // 1.
    final Directory extDir = await getTemporaryDirectory();
    final testDir = await Directory('${extDir.path}/camerawesome').create(recursive: true);

    // 2.
    if (sensors.length == 1) {
      final String filePath =
          '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
      // 3.
      return SingleCaptureRequest(filePath, sensors.first);
    } else {
      // 4.
      return MultipleCaptureRequest(
        {
          for (final sensor in sensors)
            sensor:
                '${testDir.path}/${sensor.position == SensorPosition.front ? 'front_' : "back_"}${DateTime.now().millisecondsSinceEpoch}.jpg',
        },
      );
    }
  },
  // Other params
  ...
)

There are 4 steps in this code:

  1. Create a directory where photos will be saved (here we use the temporary directory, using path_provider).
  2. Since CamerAwesome supports taking pictures with both front and back cameras at the same time, we need to detect if there is only one picture to take or several ones.
  3. If there is only one sensor used, we can build a SingleCaptureRequest with the file path and the sensor.
  4. If there are several sensors, we need to build a MultipleCaptureRequest with a map of file paths and sensors. In this case, we create a different path based on wether it's the front or back sensor that takes the picture.

The same logic goes for videos but we replace the .jpg extension with .mp4.

📷 Initial camera configuration#

You can set the initial camera configuration using a SensorConfig.

CameraAwesomeBuilder.awesome(
  sensorConfig: SensorConfig.single(
    aspectRatio: CameraAspectRatios.ratio_4_3,
    flashMode: FlashMode.auto,
    sensor: Sensor.position(SensorPosition.back),
    zoom: 0.0,
  ),
)
ParameterDescription
aspectRatioInitial aspect ratio of photos and videos taken
flashModeThe initial flash mode
sensorThe initial camera sensor (Back or Front)
zoomA value between 0.0 (no zoom) and 1.0 (max zoom)

Note: you might also notice the SensorConfig.multiple() constructor which lets you specify several sensors. This feature is in beta, but you can take a look at the dedicated documentation.

CameraAwesomeBuilder also provides a few more parameters:

  • enablePhysicalButton to enable the volume buttons to take pictures or record videos
  • filter to set an initial filter to the pictures

🎨 Customize the built-in UI#

Several parameters let you customize the built-in UI.

Theming#

You can customize the look and feel of CamerAwesome's built-in UI by setting your own theme.

For example, buttons are black with a white icon and they bounce when you tap them.

You can completely change them with AwesomeTheme:

Example:

CameraAwesomeBuilder.awesome(
  theme: AwesomeTheme(
    // Background color of the bottom actions
    bottomActionsBackgroundColor: Colors.deepPurple.withOpacity(0.5),
    // Buttons theme
    buttonTheme: AwesomeButtonTheme(
      // Background color of the button
      backgroundColor: Colors.deepPurple.withOpacity(0.5),
      // Size of the icon
      iconSize: 32,
      // Padding around the icon
      padding: const EdgeInsets.all(18),
      // Color of the icon
      foregroundColor: Colors.lightBlue,
      // Tap visual feedback (ripple, bounce...)
      buttonBuilder: (child, onTap) {
        return ClipOval(
          child: Material(
            color: Colors.transparent,
            shape: const CircleBorder(),
            child: InkWell(
              splashColor: Colors.deepPurple,
              highlightColor: Colors.deepPurpleAccent.withOpacity(0.5),
              onTap: onTap,
              child: child,
            ),
          ),
        );
      },
    ),
  ),
);

Custom theme

Let's see what happens in the above animation:

  • In the first part, the default theme of CamerAwesome is used. Note that the Flash button bounces when tapped.
  • Then, the code is changed to show the custom theme above with a hot reload.
  • Finally, the UI is updated with the new purple theme and you can see that even the Flash button has a different tap effect: it is now a ripple.

If you are using the built-in UI - even partially, it is recommended to use AwesomeTheme in the rest of your Camera UI to stay consistent.

You can access the current AwesomeTheme by using AwesomeThemeProvider:

final myTheme = AwesomeThemeProvider.of(context).theme

Tip: If you don't have a BuildContext to access the parent's AwesomeTheme, wrap your widget within a Builder widget.

See also Theming and custom_theme.dart for an example with a custom theme.

Place widgets in your UI#

The built-in UI is separated in 3 areas:

Awesome UI parts

  1. Top actions built with topActionsBuilder
  2. Middle content built with middleContentBuilder
  3. Bottom actions built with bottomActionsBuilder

You can customize each builder to return the elements that you want in each part. Feel free to reuse the included build-in widgets (aspect ratio button, flash button...).

Here is an example of what you can do:

CameraAwesomeBuilder.awesome(
  // Other parameters
  ...
  // Set an AwesomeTheme that you might reuse in your UI
  theme: AwesomeTheme(
    bottomActionsBackgroundColor: Colors.cyan.withOpacity(0.5),
    buttonTheme: AwesomeButtonTheme(
      backgroundColor: Colors.cyan.withOpacity(0.5),
      iconSize: 20,
      foregroundColor: Colors.white,
      padding: const EdgeInsets.all(16),
      // Tap visual feedback (ripple, bounce...)
      buttonBuilder: (child, onTap) {
        return ClipOval(
          child: Material(
            color: Colors.transparent,
            shape: const CircleBorder(),
            child: InkWell(
              splashColor: Colors.cyan,
              highlightColor: Colors.cyan.withOpacity(0.5),
              onTap: onTap,
              child: child,
            ),
          ),
        );
      },
    ),
  ),
  // Show the filter button on the top part of the screen
  topActionsBuilder: (state) => AwesomeTopActions(
    padding: EdgeInsets.zero,
    state: state,
    children: [
      Expanded(
        child: AwesomeFilterWidget(
          state: state,
          filterListPosition: FilterListPosition.aboveButton,
          filterListPadding: const EdgeInsets.only(top: 8),
        ),
      ),
    ],
  ),
  // Show some Text with same background as the bottom part
  middleContentBuilder: (state) {
    return Column(
      children: [
        const Spacer(),
        // Use a Builder to get a BuildContext below AwesomeThemeProvider widget
        Builder(builder: (context) {
          return Container(
            // Retrieve your AwesomeTheme's background color
            color: AwesomeThemeProvider.of(context)
                .theme
                .bottomActionsBackgroundColor,
            child: const Align(
              alignment: Alignment.bottomCenter,
              child: Padding(
                padding: EdgeInsets.only(bottom: 10, top: 10),
                child: Text(
                  "Take your best shot!",
                  style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                    fontStyle: FontStyle.italic,
                  ),
                ),
              ),
            ),
          );
        }),
      ],
    );
  },
  // Show Flash button on the left and Camera switch button on the right
  bottomActionsBuilder: (state) => AwesomeBottomActions(
    state: state,
    left: AwesomeFlashButton(
      state: state,
    ),
    right: AwesomeCameraSwitchButton(
      state: state,
      scale: 1.0,
      onSwitchTap: (state) {
        state.switchCameraSensor(
          aspectRatio: state.sensorConfig.aspectRatio,
        );
      },
    ),
  ),
)

Custom awesome UI

ParameterDescription
topActionsBuilderTop part of the built-in UI
middleContentBuilderContent between top and bottom parts of the built-in UI
bottomActionsBuilderBottom part of the built-in UI

See custom_awesome_ui.dart for an example using these parameters.

Camera preview positioning#

Use these parameters to adjust where you want to place the camera preview in your UI.

CameraAwesomeBuilder.awesome(
  previewPadding: const EdgeInsets.all(20),
  previewAlignment: Alignment.center,
  // Other parameters
  ...
)

Custom preview positioning

By default, preview is centered with no padding.

ParameterDescription
previewPaddingAdd padding around the preview
previewAlignmentAlign the preview

You can see these parameters in use in custom_awesome_ui.dart example.

Add additional content on top of the preview#

If you'd like to place elements on top of the preview, like you would do it in a Stack, use the previewDecoratorBuilder.

This feature can be used combined with image analysis to show face filters (like snapchat ones) for example:

Face filter using previewDecoratorBuilder

ParameterDescription
previewDecoratorBuilderAdd a widget on top of the preview

That's what is used in ai_analysis_faces.dart to draw contours of the detected face.

📝 Complete example#

Here is an example showing the complete list of parameters you can set to customize your camera experience:

CameraAwesomeBuilder.awesome(
  // Bottom actions (take photo, switch camera...)
  bottomActionsBuilder: (state) {
    return AwesomeBottomActions(
      state: state,
      onMediaTap: _handleMediaTap,
    );
  },
  // Clicking on volume buttons will capture photo/video depending on the current mode
  enablePhysicalButton: true,
  // Filter to apply on the preview
  filter: AwesomeFilter.AddictiveRed,
   // Image analysis configuration
  imageAnalysisConfig: AnalysisConfig(
        androidOptions: const AndroidAnalysisOptions.nv21(
            width: 1024,
        ),
        autoStart: true,
  ),
  // Middle content (filters, photo/video switcher...)
  middleContentBuilder: (state) {
    // Use this to add widgets on the middle of the preview
    return Column(
      children: [
        const Spacer(),
        AwesomeFilterWidget(state: state),
        Builder(
          builder: (context) => Container(
            color: AwesomeThemeProvider.of(context)
                .theme
                .bottomActionsBackgroundColor,
            height: 8,
          ),
        ),
        AwesomeCameraModeSelector(state: state),
      ],
    );
  },
  // Handle image analysis
  onImageForAnalysis: (analysisImage) {
    // Do some stuff with the image (see example)
    return processImage(analysisImage);
  },
  onMediaTap: (mediaCapture) {
    // Hande tap on the preview of the last media captured
    print('Tap on ${mediaCapture.filePath}');
  },
  // Handle gestures on the preview, such as tap to focus or scale to zoom
  onPreviewTapBuilder: (state) => OnPreviewTap(
    onTap: (position, flutterPreviewSize, pixelPreviewSize) {
      // Handle tap to focus (default) or take a photo for instance
      // ...
    },
    onTapPainter: (position) {
      // Tap feedback, here we just show a circle
      return Positioned(
        left: position.dx - 25,
        top: position.dy - 25,
        child: IgnorePointer(
          child: Container(
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              border: Border.all(color: Colors.white, width: 2),
            ),
            width: 50,
            height: 50,
          ),
        ),
      );
    },
    // Duration during which the feedback should be shown
    tapPainterDuration: const Duration(seconds: 2),
  ),
  // Handle scale gestures on the preview
  onPreviewScaleBuilder: (state) => OnPreviewScale(
    onScale: (scale) {
      // Do something with the scale value, set zoom for instance
      state.sensorConfig.setZoom(scale);
    },
  ),
  // Alignment of the preview
  previewAlignment: Alignment.center,
    // Add your own decoration on top of the preview
  previewDecoratorBuilder: (state, preview) {
    // This will be shown above the preview (in a Stack)
    // It could be used in combination with MLKit to draw filters on faces for example
    return PreviewDecorationWiget(preview.rect);
  },
  // Preview fit of the camera
  previewFit: CameraPreviewFit.fitWidth,
  // Padding around the preview
  previewPadding: const EdgeInsets.all(20),
  // Show a progress indicator while loading the camera
  progressIndicator: const Center(
    child: SizedBox(
      width: 100,
      height: 100,
      child: CircularProgressIndicator(),
    ),
  ),
   // Define if you want to take photos, videos or both and where to save them
  saveConfig: SaveConfig.photoAndVideo(
    initialCaptureMode: CaptureMode.photo,
    mirrorFrontCamera: true,
    photoPathBuilder: (sensors) async {
      // Return a valid file path (must be a jpg file)
      return SingleCaptureRequest('some/image/file/path.jpg', sensors.first);
    },
    videoPathBuilder: (sensors) async {
      // Return a valid file path (must be a mp4 file)
      return SingleCaptureRequest('some/image/file/path.mp4', sensors.first);
    },
  ),
  // Sensor initial configuration
  sensorConfig: SensorConfig.single(
    aspectRatio: CameraAspectRatios.ratio_4_3,
    flashMode: FlashMode.auto,
    sensor: Sensor.position(SensorPosition.back),
    zoom: 0.0,
  ),
  // CamerAwesome theme used to customize the built-in UI
  theme: AwesomeTheme(
    // Background color of the bottom actions
    bottomActionsBackgroundColor: Colors.deepPurple.withOpacity(0.5),
    // Buttons theme
    buttonTheme: AwesomeButtonTheme(
      // Background color of the buttons
      backgroundColor: Colors.deepPurple.withOpacity(0.5),
      // Buttons icon size
      iconSize: 32,
      // Padding around icons
      padding: const EdgeInsets.all(18),
      // Buttons icon color
      foregroundColor: Colors.lightBlue,
      // Tap visual feedback (ripple, bounce...)
      buttonBuilder: (child, onTap) {
        return ClipOval(
          child: Material(
            color: Colors.transparent,
            shape: const CircleBorder(),
            child: InkWell(
              splashColor: Colors.deepPurple,
              highlightColor: Colors.deepPurpleAccent.withOpacity(0.5),
              onTap: onTap,
              child: child,
            ),
          ),
        );
      },
    ),
  ),
  // Top actions (flash, timer...)
  topActionsBuilder: (state) {
    return AwesomeTopActions(state: state);
  },
  // default filter
  defaultFilter: AwesomeFilter.None,
  // list of photo filters (default to awesome filter) 
  // put null or empty list for hiding filters 
  availableFilters: awesomePresetFiltersList,
)

🔬 Full list of properties#

MethodComment
aspectRatioInitial aspect ratio of photos and videos taken
bottomActionsBuilderA widget builder used to show buttons on the bottom of the preview.
AwesomeBottomActions by default.
enablePhysicalButtonWhen set to true, volume buttons will capture pictures/videos depending on the current mode.
filterInitial preview filter which will be applied to the photo
imageAnalysisConfigImage format, resolution and autoStart (start analysis immediately or later)
middleContentBuilderA widget builder used to add widgets above the middle part of the preview (between bottom and top actions).
Shows the filter selector by default.
onImageForAnalysisCallback that will provide an image stream for AI analysis
onMediaTapChoose what you want to do when user tap on the last media captured
onPreviewTapBuilderCustomize the behavior when the camera preview is tapped (tap to focus by default)
onPreviewScaleBuilderCustomize what to do when the user makes a pinch (pinch to zoom by default)
previewAlignmentAlignment of the preview
previewDecoratorBuilderA widget builder used to draw elements around or on top of the preview
previewFitOne of fitWidth, fitHeight, contain, cover
previewPaddingPadding around the preview
progressIndicatorWidget to show when loading
saveConfigDefine if you want to take photos, videos or both and where to save them. You can also set exif preferences, decide to mirror or not front camera outputs and set video recording settings.
sensorConfigThe initial sensor configuration: aspect ratio, flash mode, which sensor to use and initial zoom.
themeTheme used to customize the built-in UI
topActionsBuilderA widget builder used to show buttons on the top of the preview.
AwesomeTopActions by default.

🔨 Need more customization? Other use cases?#

If the separation between top, middle and bottom content doesn't suit your needs, you can also provide your own UI using CameraAwesomeBuilder.custom() builder.

See 🎨 Creating a custom UI documentation.

If you only want to display the preview and not taking pictures or videos, you can also use CameraAwesomeBuilder.previewOnly() constructor. You can check Reading barcodes and Detecting faces examples to see how to use it.

If you are only interested in image analysis, you can also use CameraAwesomeBuilder.analysisOnly() which will not provide a camera preview behind your builder. In analysis_image_filter.dart and analysis_image_filter_picker.dart, this constructor is used to draw a camera preview with filter effects applied to each image (image distortion, greyscale, etc...). You can find more about this example in the Image analysis formats and conversions documentation.