F.SO7
F.SO7

Reputation: 925

How to achieve an expandable bottom navigation bar in Flutter

I am trying to build an app that includes a bottom navigation bar for navigating between screens.

In the middle of the bottom navigation bar, I want to add a button which expands the bottom navigation bar with a semicircle, and revels more buttons.

I've read the documentation of the bottom navigation bar, and searched a lot in pub.dev if there is something similar I can use, but I couldn't find any.

Does anyone know if it's achievable, and if so, how?

Thank you very much

Upvotes: 0

Views: 1693

Answers (2)

Osama Abdelaziz
Osama Abdelaziz

Reputation: 140

I think you need to learn about Flutter Animated Radial Menu and how to implement it in your code, you can go with this article and try to implement in your way.

Upvotes: 0

Bach
Bach

Reputation: 3326

You can check this simple implementation with showDialog and CustomPainter. Basically it involved displaying a showDialog with bottom padding equals the height of BottomNavigationBar, then arrange the items within a Stack. The half circle is drawn using CustomPainter.

Full example app:

import 'dart:math' as math;

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Test App'),
      ),
      bottomNavigationBar: BottomNavigationBar(
        unselectedItemColor: Colors.grey,
        selectedItemColor: Colors.blue,
        showUnselectedLabels: true,
        selectedFontSize: 14,
        unselectedFontSize: 14,
        type: BottomNavigationBarType.fixed,
        onTap: (index) {
          if (index == 2) {
            final diameter = 200.0;
            final iconSize = 40;
            showDialog(
              context: context,
              barrierDismissible: true,
              barrierColor: Colors.grey.withOpacity(0.1),
              builder: (context) => Material(
                color: Colors.transparent,
                child: Stack(
                  alignment: AlignmentDirectional.bottomCenter,
                  children: [
                    Container(
                      width: diameter + iconSize,
                      height: diameter / 1.5,
                      alignment: Alignment.bottomCenter,
                      margin:
                          EdgeInsets.only(bottom: kBottomNavigationBarHeight),
                      child: Stack(
                        children: [
                          Container(
                              alignment: Alignment.bottomCenter,
                              child: MyArc(diameter: diameter)),
                          Positioned(
                            left: 0,
                            bottom: 10,
                            child: _buildButton(),
                          ),
                          Positioned(
                            left: diameter / 4,
                            top: 10,
                            child: _buildButton(),
                          ),
                          Positioned(
                            right: diameter / 4,
                            top: 10,
                            child: _buildButton(),
                          ),
                          Positioned(
                            right: 0,
                            bottom: 10,
                            child: _buildButton(),
                          )
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            );
          }
        },
        items: List<BottomNavigationBarItem>.generate(
          5,
          (index) =>
              BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
        ),
      ),
    );
  }

  _buildButton() {
    return Container(
      constraints: BoxConstraints.tightFor(width: 40, height: 60),
      child: Column(
        children: [
          Text(
            'Title',
            style: TextStyle(fontSize: 12),
          ),
          SizedBox(height: 3),
          CircleAvatar(
            backgroundColor: Colors.white,
            child: Icon(Icons.home),
          ),
        ],
      ),
    );
  }
}

class MyArc extends StatelessWidget {
  final double diameter;

  const MyArc({Key key, this.diameter = 200}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyPainter(),
      size: Size(diameter, diameter),
    );
  }
}

// This is the Painter class
class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..shader = RadialGradient(
        colors: [
          Colors.blue,
          Colors.purpleAccent.withOpacity(0.4),
        ],
      ).createShader(Rect.fromCircle(
        center: Offset(size.width / 2, size.height),
        radius: 200,
      ));
    canvas.drawArc(
      Rect.fromCenter(
        center: Offset(size.width / 2, size.height),
        height: size.height * 1.5,
        width: size.width,
      ),
      math.pi,
      math.pi,
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Result:
enter image description here

Upvotes: 2

Related Questions