Search code examples
flutterflutter-layoutbottomnavigationviewflutter-bottomnavigation

How to create this type of bottom navigation bar with curved background?


enter image description here

I have tried some packages and stack with image but it is not working for me. Any help would be great.

Background image with curve is not working.


Solution

  • Does this work for you?

    enter image description here

    You should be able to do that with a custom NotchedShape:

    class CustomNotchedShape extends NotchedShape {
      const CustomNotchedShape();
    
      @override
      Path getOuterPath(Rect host, Rect guest) {
        final x = math.min(host.width / 20, host.height / 3);
        final guestMargin = guest == null ? 0 : 1.0;
        if (guest == null || !host.overlaps(guest)) {
          guest = Rect.fromCenter(
              center: Offset(host.width / 2, host.top), width: 0, height: 0);
        }
    
        num degToRad(num deg) => deg * (math.pi / 180.0);
    
        return Path()
          ..moveTo(host.left, host.bottom)
          ..quadraticBezierTo(
              host.left + x, host.bottom, host.left + x, host.bottom - x)
          ..lineTo(host.left + x, host.top + x)
          ..quadraticBezierTo(host.left + x, host.top, host.left + 2 * x, host.top)
          ..lineTo(guest.left, host.top)
          ..arcTo(
              Rect.fromCenter(
                  center: guest.center,
                  width: guest.width + 2 * guestMargin,
                  height: guest.width + 2 * guestMargin),
              degToRad(180),
              degToRad(-180),
              false)
          ..lineTo(host.right - 2 * x, host.top)
          ..quadraticBezierTo(
              host.right - x, host.top, host.right - x, host.top + x)
          ..lineTo(host.right - x, host.bottom - x)
          ..quadraticBezierTo(host.right - x, host.bottom, host.right, host.bottom)
          ..close();
      }
    }
    

    Then, you use this CustomNotchedShape as the shape of your BottomAppBar:

    class MyBottomNavigationBar extends HookWidget {
      @override
      Widget build(BuildContext context) {
        final _currentIndex = useState(1);
        void navigateTo(int index) => _currentIndex.value = index;
        bool active(int index) => _currentIndex.value == index;
    
        return BottomAppBar(
          color: Theme.of(context).primaryColor,
          shape: CustomNotchedShape(),
          child: Container(
            height: 50,
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20.0),
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    IconButton(
                      onPressed: () => navigateTo(0),
                      icon: Icon(Icons.home_outlined),
                      color: active(0) ? Colors.white : Colors.black,
                    ),
                    IconButton(
                      onPressed: () => navigateTo(1),
                      icon: Icon(Icons.horizontal_split_outlined),
                      color: active(1) ? Colors.white : Colors.black,
                    ),
                    Padding(
                      padding: EdgeInsets.only(top: 24.0),
                      child: Text('New task'),
                    ),
                    IconButton(
                      onPressed: () => navigateTo(2),
                      icon: Icon(Icons.access_time_outlined),
                      color: active(2) ? Colors.white : Colors.black,
                    ),
                    IconButton(
                      onPressed: () => navigateTo(3),
                      icon: Icon(Icons.settings_outlined),
                      color: active(3) ? Colors.white : Colors.black,
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    

    Full source code

    For easy copy-paste.

    import 'dart:math' as math;
    
    import 'package:flutter/material.dart';
    import 'package:flutter_hooks/flutter_hooks.dart';
    
    void main() {
      runApp(
        MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            scaffoldBackgroundColor: Color(0xffd3ccca),
            primaryColor: Color(0xff86736c),
            accentColor: Color(0xff76504e),
          ),
          title: 'Flutter Demo',
          home: Scaffold(
            body: MyContent(),
            bottomNavigationBar: MyBottomNavigationBar(),
            floatingActionButton: FloatingActionButton(
              mini: true,
              onPressed: () => print('Adding new task...'),
              child: Icon(Icons.add),
            ),
            floatingActionButtonLocation:
                FloatingActionButtonLocation.miniCenterDocked,
          ),
        ),
      );
    }
    
    class MyContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(child: Text('Does this work for you?'));
      }
    }
    
    class MyBottomNavigationBar extends HookWidget {
      @override
      Widget build(BuildContext context) {
        final _currentIndex = useState(1);
        void navigateTo(int index) => _currentIndex.value = index;
        bool active(int index) => _currentIndex.value == index;
    
        return BottomAppBar(
          color: Theme.of(context).primaryColor,
          shape: CustomNotchedShape(),
          child: Container(
            height: 50,
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20.0),
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    IconButton(
                      onPressed: () => navigateTo(0),
                      icon: Icon(Icons.home_outlined),
                      color: active(0) ? Colors.white : Colors.black,
                    ),
                    IconButton(
                      onPressed: () => navigateTo(1),
                      icon: Icon(Icons.horizontal_split_outlined),
                      color: active(1) ? Colors.white : Colors.black,
                    ),
                    Padding(
                      padding: EdgeInsets.only(top: 24.0),
                      child: Text('New task'),
                    ),
                    IconButton(
                      onPressed: () => navigateTo(2),
                      icon: Icon(Icons.access_time_outlined),
                      color: active(2) ? Colors.white : Colors.black,
                    ),
                    IconButton(
                      onPressed: () => navigateTo(3),
                      icon: Icon(Icons.settings_outlined),
                      color: active(3) ? Colors.white : Colors.black,
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class CustomNotchedShape extends NotchedShape {
      const CustomNotchedShape();
    
      @override
      Path getOuterPath(Rect host, Rect guest) {
        final x = math.min(host.width / 20, host.height / 3);
        final guestMargin = guest == null ? 0 : 1.0;
        if (guest == null || !host.overlaps(guest)) {
          guest = Rect.fromCenter(
              center: Offset(host.width / 2, host.top), width: 0, height: 0);
        }
    
        num degToRad(num deg) => deg * (math.pi / 180.0);
    
        return Path()
          ..moveTo(host.left, host.bottom)
          ..quadraticBezierTo(
              host.left + x, host.bottom, host.left + x, host.bottom - x)
          ..lineTo(host.left + x, host.top + x)
          ..quadraticBezierTo(host.left + x, host.top, host.left + 2 * x, host.top)
          ..lineTo(guest.left, host.top)
          ..arcTo(
              Rect.fromCenter(
                  center: guest.center,
                  width: guest.width + 2 * guestMargin,
                  height: guest.width + 2 * guestMargin),
              degToRad(180),
              degToRad(-180),
              false)
          ..lineTo(host.right - 2 * x, host.top)
          ..quadraticBezierTo(
              host.right - x, host.top, host.right - x, host.top + x)
          ..lineTo(host.right - x, host.bottom - x)
          ..quadraticBezierTo(host.right - x, host.bottom, host.right, host.bottom)
          ..close();
      }
    }