Giant Brain
Giant Brain

Reputation: 124

How to make floating BottomAppBar?

I am working in flutter app. But I am having hard time with Flutter UI because it doesn't exactly match the UI.

This is the UI design.

This is the UI design

And this is current flutter UI.

This is current flutter UI

This is my code.

      Widget buildBottomBar() {
    return BottomAppBar(
      elevation: 0.0,
      shape: const CircularNotchedRectangle(),
      notchMargin: 5.0,
      clipBehavior: Clip.antiAlias,
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(
            width: 10,
            color: Colors.transparent,
          ),
        ),
        child: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          iconSize: 28,
          elevation: 0.0,
          currentIndex: selectedIndex,
          selectedLabelStyle: robotoStyle(FontWeight.w500,
              const Color.fromARGB(255, 49, 48, 54), null, null),
          showUnselectedLabels: true,
          unselectedLabelStyle:
              robotoStyle(FontWeight.w500, const Color(0xff313036), null, null),
          unselectedItemColor: const Color(0xff313036),
          selectedItemColor: const Color(0xff313036),
          selectedIconTheme: const IconThemeData(color: Color(0xffff2323)),
          onTap: onBottomBarButtonTapped,
          backgroundColor: Colors.transparent,
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              activeIcon: Icon(
                Icons.signal_cellular_alt_rounded,
                color: Color(0xffff2323),
              ),
              icon: Icon(
                Icons.signal_cellular_alt_rounded,
                color: Color(0xff313036),
              ),
              label: "Trend",
            ),
            BottomNavigationBarItem(
              activeIcon: Icon(
                CupertinoIcons.calendar,
                color: Color(0xffff2323),
              ),
              icon: Icon(
                CupertinoIcons.calendar,
                color: Color(0xff313036),
              ),
              label: "Calendar",
            ),
            BottomNavigationBarItem(
              activeIcon: Icon(
                Icons.search_rounded,
                color: Colors.transparent,
              ),
              icon: Icon(
                Icons.search_rounded,
                color: Colors.transparent,
              ),
              label: "",
            ),
            BottomNavigationBarItem(
              activeIcon: Icon(
                CupertinoIcons.arrow_2_circlepath,
                color: Color(0xffff2323),
              ),
              icon: Icon(
                CupertinoIcons.arrow_2_circlepath,
                color: Color(0xff313036),
              ),
              label: "Restock",
            ),
            BottomNavigationBarItem(
              activeIcon: Icon(
                Icons.account_circle,
                color: Color(0xffff2323),
              ),
              icon: Icon(
                Icons.account_circle,
                color: Color(0xff313036),
              ),
              label: "Account",
            ),
          ],
        ),
      ),
      // ),
    );
  }

 Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      resizeToAvoidBottomInset: false,
      appBar: null,
      backgroundColor: Colors.white,
      body: ...,
      bottomNavigationBar: buildBottomBar(),
      floatingActionButton: Padding(
        padding: const EdgeInsets.all(5.0),
        child: FloatingActionButton(...)
      ),
  }

And the next problem is just the radius under the floating search button. How to apply border radius like this design?

Let me know how to do it as soon as possible.

Upvotes: 1

Views: 1311

Answers (1)

Giant Brain
Giant Brain

Reputation: 124

I solved it by customizing the standard package. The reason for this phenomenon appears because the edge is pushed to the right by the applied Margin or Padding. That is, the solution is to move the clipper left or right as much as the applied Margin or Padding. Then I will present a step-by-step solution below.

  1. Press Ctrl+B (for Android Studio) or F12 (for VS Code) on BottomAppBar().
  1. Click target button.

    enter image description here

  1. Copy bottom_app_bar.dart to your directory of custom libraries then rename to custom_bottom_app_bar.dart.

enter image description here

  1. Modify part of the code in the new custom_bottom_app_bar.dart.

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

  1. All done. Enjoy new widget. enter image description here
  1. I'll add the full example code below.

custom_bottom_app_bar.dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.*

import 'package:flutter/foundation.dart';
// import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';

// import 'bottom_app_bar_theme.dart';
// import 'elevation_overlay.dart';
// import 'package:flutter/material.dart';
// import 'scaffold.dart';
// import 'theme.dart';

// Examples can assume:
// late Widget bottomAppBarContents;

/// A container that is typically used with [Scaffold.bottomNavigationBar], and
/// can have a notch along the top that makes room for an overlapping
/// [FloatingActionButton].
///
/// Typically used with a [Scaffold] and a [FloatingActionButton].
///
/// {@tool snippet}
/// ```dart
/// Scaffold(
///   bottomNavigationBar: CustomBottomAppBar(
///     color: Colors.white,
///     child: bottomAppBarContents,
///   ),
///   floatingActionButton: const FloatingActionButton(onPressed: null),
/// )
/// ```
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows the [CustomBottomAppBar], which can be configured to have a notch using the
/// [CustomBottomAppBar.shape] property. This also includes an optional [FloatingActionButton], which illustrates
/// the [FloatingActionButtonLocation]s in relation to the [CustomBottomAppBar].
///
/// ** See code in examples/api/lib/material/bottom_app_bar/bottom_app_bar.1.dart **
/// {@end-tool}
///
/// See also:
///
///  * [NotchedShape] which calculates the notch for a notched [CustomBottomAppBar].
///  * [FloatingActionButton] which the [CustomBottomAppBar] makes a notch for.
///  * [AppBar] for a toolbar that is shown at the top of the screen.
class CustomBottomAppBar extends StatefulWidget {
  /// Creates a bottom application bar.
  ///
  /// The [clipBehavior] argument defaults to [Clip.none] and must not be null.
  /// Additionally, [elevation] must be non-negative.
  ///
  /// If [color], [elevation], or [shape] are null, their [BottomAppBarTheme] values will be used.
  /// If the corresponding [BottomAppBarTheme] property is null, then the default
  /// specified in the property's documentation will be used.
  const CustomBottomAppBar({
    super.key,
    this.color,
    this.elevation,
    this.shape,
    this.clipBehavior = Clip.none,
    this.notchMargin = 4.0,
    this.child,
    this.positionInHorizontal = 0.0,
  }) : assert(elevation == null || elevation >= 0.0);

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  ///
  /// Typically this the child will be a [Row], with the first child
  /// being an [IconButton] with the [Icons.menu] icon.
  final Widget? child;

  /// The bottom app bar's background color.
  ///
  /// If this property is null then [BottomAppBarTheme.color] of
  /// [ThemeData.bottomAppBarTheme] is used. If that's null then
  /// [ThemeData.bottomAppBarColor] is used.
  final Color? color;

  /// The z-coordinate at which to place this bottom app bar relative to its
  /// parent.
  ///
  /// This controls the size of the shadow below the bottom app bar. The
  /// value is non-negative.
  ///
  /// If this property is null then [BottomAppBarTheme.elevation] of
  /// [ThemeData.bottomAppBarTheme] is used. If that's null, the default value
  /// is 8.
  final double? elevation;

  /// The notch that is made for the floating action button.
  ///
  /// If this property is null then [BottomAppBarTheme.shape] of
  /// [ThemeData.bottomAppBarTheme] is used. If that's null then the shape will
  /// be rectangular with no notch.
  final NotchedShape? shape;

  /// {@macro flutter.material.Material.clipBehavior}
  ///
  /// Defaults to [Clip.none], and must not be null.
  final Clip clipBehavior;

  /// The margin between the [FloatingActionButton] and the [CustomBottomAppBar]'s
  /// notch.
  ///
  /// Not used if [shape] is null.
  final double notchMargin;

  final double positionInHorizontal;

  @override
  State createState() => _CustomBottomAppBarState();
}

class _CustomBottomAppBarState extends State<CustomBottomAppBar> {
  late ValueListenable<ScaffoldGeometry> geometryListenable;
  final GlobalKey materialKey = GlobalKey();
  static const double _defaultElevation = 8.0;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    geometryListenable = Scaffold.geometryOf(context);
  }

  @override
  Widget build(BuildContext context) {
    final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
    final bool hasFab = Scaffold.of(context).hasFloatingActionButton;
    final NotchedShape? notchedShape = widget.shape ?? babTheme.shape;
    final CustomClipper<Path> clipper = notchedShape != null && hasFab
        ? _BottomAppBarClipper(
            geometry: geometryListenable,
            shape: notchedShape,
            materialKey: materialKey,
            notchMargin: widget.notchMargin,
            position2MoveHorizontal: widget.positionInHorizontal,
          )
        : const ShapeBorderClipper(shape: RoundedRectangleBorder());
    final double elevation =
        widget.elevation ?? babTheme.elevation ?? _defaultElevation;
    final Color color =
        widget.color ?? babTheme.color ?? Theme.of(context).bottomAppBarColor;
    final Color effectiveColor =
        ElevationOverlay.applyOverlay(context, color, elevation);
    return PhysicalShape(
      clipper: clipper,
      elevation: elevation,
      color: effectiveColor,
      clipBehavior: widget.clipBehavior,
      child: Material(
        key: materialKey,
        type: MaterialType.transparency,
        child: widget.child == null ? null : SafeArea(child: widget.child!),
      ),
    );
  }
}

class _BottomAppBarClipper extends CustomClipper<Path> {
  const _BottomAppBarClipper({
    required this.geometry,
    required this.shape,
    required this.materialKey,
    required this.notchMargin,
    required this.position2MoveHorizontal,
  }) : super(reclip: geometry);

  final ValueListenable<ScaffoldGeometry> geometry;
  final NotchedShape shape;
  final GlobalKey materialKey;
  final double notchMargin;
  final double position2MoveHorizontal;

  // Returns the top of the CustomBottomAppBar in global coordinates.
  //
  // If the Scaffold's bottomNavigationBar was specified, then we can use its
  // geometry value, otherwise we compute the location based on the AppBar's
  // Material widget.
  double get bottomNavigationBarTop {
    final double? bottomNavigationBarTop =
        geometry.value.bottomNavigationBarTop;
    if (bottomNavigationBarTop != null) {
      return bottomNavigationBarTop;
    }
    final RenderBox? box =
        materialKey.currentContext?.findRenderObject() as RenderBox?;
    return box?.localToGlobal(Offset.zero).dy ?? 0;
  }

  @override
  Path getClip(Size size) {
    // button is the floating action button's bounding rectangle in the
    // coordinate system whose origin is at the appBar's top left corner,
    // or null if there is no floating action button.

    final Rect? button = geometry.value.floatingActionButtonArea
        ?.translate(position2MoveHorizontal, bottomNavigationBarTop * -1.0);
    return shape.getOuterPath(Offset.zero & size, button?.inflate(notchMargin));
  }

  @override
  bool shouldReclip(_BottomAppBarClipper oldClipper) {
    return oldClipper.geometry != geometry ||
        oldClipper.shape != shape ||
        oldClipper.notchMargin != notchMargin;
  }
}

home.dart

Widget buildBottomBar() {
    Map bottomAppBarMargin = {
      'left': 20.0,
      'right': 20.0,
      'bottom': 12.0,
      'top': 0.0,
    };
    return Container(
      margin: EdgeInsets.only(
        left: bottomAppBarMargin['left'],
        right: bottomAppBarMargin['right'],
        bottom: bottomAppBarMargin['bottom'],
        top: bottomAppBarMargin['top'],
      ),
      child: CustomBottomAppBar(
        elevation: 0.0,
        positionInHorizontal: (0.0-bottomAppBarMargin['left']),
        color: Colors.white,
        shape: const CircularNotchedRectangle(),
        notchMargin: 5.0,
        clipBehavior: Clip.antiAlias,
        child: SizedBox(...)
      ),
    );
}

Result

enter image description here

Upvotes: 1

Related Questions