Search code examples
flutterflutter-layoutbottomnavigationviewrounded-cornersbottomappbar

How to add rounded corners to BottomAppBar with CircularNotchedRectangle in Flutter


I want to create BottomAppBar with rounded corners, circular notched rectangle and material shadow. The problem is that when I add rounded corners to BottomAppBar by wrapping it with ClipRRect widget I lose material shadow.

BottomAppBar without rounded corners, but with Material shadow. Please ignore wrong aligned notch. enter image description here

BottomAppBar wrapped in ClipRRect widget, but without shadow. Again please ignore wrong aligned notch: enter image description here

How to achieve rounded corners with notch and material shadow?

Full code:

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: Scaffold(
        extendBody: true,
        body: Container(
          color: Colors.amber,
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
        floatingActionButton: Transform.translate(
          offset: Offset(0, -10),
          child: FloatingActionButton(
            onPressed: () {},
            child: Icon(Icons.add),
            elevation: 2.0,
          ),
        ),
        bottomNavigationBar: BottomNavigationView(),
      ),
    );
  }
}

class BottomNavigationView extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Transform.translate(
      offset: Offset(0.0, -10),
      child: Container(
        margin: EdgeInsets.only(left: 20, right: 20),
        child: ClipRRect(
          borderRadius: BorderRadius.all(Radius.circular(30),
          ),
          child: BottomAppBar(
            shape: CircularNotchedRectangle(),
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.accessibility_new),
                  ],
                ),
                _buildMiddleTabItem(),
                Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.accessibility_new),
                  ],
                )
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildMiddleTabItem() {
    return Expanded(
      child: SizedBox(
        height: 60,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(height: 24),
            Text(''),
          ],
        ),
      ),
    );
  }
}

Solution

  • You can create your own ShapeBorder.

    enter image description here

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            extendBody: true,
            body: Container(
              color: Colors.amber,
            ),
            floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
            floatingActionButton: Transform.translate(
              offset: Offset(0, -10),
              child: FloatingActionButton(
                onPressed: () {},
                child: Icon(Icons.add),
                elevation: 2.0,
              ),
            ),
            bottomNavigationBar: BottomNavigationView(),
          ),
        );
      }
    }
    
    class BottomNavigationView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Transform.translate(
          offset: Offset(0.0, -10),
          child: Container(
            margin: EdgeInsets.symmetric(horizontal: 10),
            padding: EdgeInsets.symmetric(horizontal: 20),
            decoration: ShapeDecoration(
              color: Colors.white,
              shape: MyBorderShape(),
              shadows: [
                BoxShadow(
                    color: Colors.black38, blurRadius: 8.0, offset: Offset(1, 1)),
              ],
            ),
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.accessibility_new),
                  ],
                ),
                _buildMiddleTabItem(),
                Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.accessibility_new),
                  ],
                )
              ],
            ),
          ),
        );
      }
    
      Widget _buildMiddleTabItem() {
        return Expanded(
          child: SizedBox(
            height: 60,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                SizedBox(height: 24),
                Text(''),
              ],
            ),
          ),
        );
      }
    }
    
    class MyBorderShape extends ShapeBorder {
      MyBorderShape();
    
      @override
      EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
    
      @override
      Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
    
      double holeSize = 70;
    
      @override
      Path getOuterPath(Rect rect, {TextDirection textDirection}) {
        print(rect.height);
        return Path.combine(
          PathOperation.difference,
          Path()
            ..addRRect(
                RRect.fromRectAndRadius(rect, Radius.circular(rect.height / 2)))
            ..close(),
          Path()
            ..addOval(Rect.fromCenter(
                center: rect.center.translate(0, -rect.height / 2),
                height: holeSize,
                width: holeSize))
            ..close(),
        );
      }
    
      @override
      void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
    
      @override
      ShapeBorder scale(double t) => this;
    }