Home > Net >  How to create stylish AppBar like that?
How to create stylish AppBar like that?

Time:02-22

How to create stylish AppBar in flutter?

How to create stylish Tabs on AppBar like this in Flutter?

CodePudding user response:

I think the tricky part in this AppBar is the overlapping of the primary navigation items. This could be achieved using an OverflowBox.

Then, for the bottom curves of the active navigation item, you could use a CustomClipper.

enter image description here

This sample has minimum interactivity, you may click on the primary navigation items to toggle the active one.

Full source code

Just copy-paste this in the main.dart file of a new Flutter project.

import 'package:flutter/material.dart';

// Constants
const kGap = 6.0;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final theme = ThemeData.light();
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Umyt App',
      theme: theme.copyWith(
        colorScheme: theme.colorScheme.copyWith(
          primary: const Color(0xFFED6324),
          secondary: const Color(0xFF611B63),
        ),
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CustomAppBar(),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: const [
            Text("Hello, Umyt!"),
            Text("I hope this helps you."),
          ],
        ),
      ),
    );
  }
}

class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
  const CustomAppBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Theme.of(context).colorScheme.primary,
      child: SafeArea(
        child: Column(
          children: const [
            SizedBox(height: kGap),
            PrimaryNavBar(),
            SizedBox(
                height: kGap *
                    0.96), // Hack without which I have a thin line on the emulator.
            SecondaryNavBar(),
          ],
        ),
      ),
    );
  }

  @override
  Size get preferredSize => const Size.fromHeight(106);
}

class PrimaryNavBar extends StatefulWidget {
  const PrimaryNavBar({Key? key}) : super(key: key);

  @override
  State<PrimaryNavBar> createState() => _PrimaryNavBarState();
}

class _PrimaryNavBarState extends State<PrimaryNavBar> {
  String _activeItem = 'market';

  void _toggleActiveItem(String item) {
    setState(() => _activeItem = item);
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const SizedBox(width: kGap),
        PrimaryNavItem(
          onTap: _toggleActiveItem,
          title: 'market',
          activeItem: _activeItem,
        ),
        const SizedBox(width: kGap),
        PrimaryNavItem(
          onTap: _toggleActiveItem,
          title: 'store',
          activeItem: _activeItem,
        ),
      ],
    );
  }
}

class SecondaryNavBar extends StatelessWidget {
  const SecondaryNavBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding:
          const EdgeInsets.symmetric(horizontal: 2 * kGap, vertical: 2 * kGap),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          const SecondaryNavItem(
              icon: Icons.location_on_outlined, text: 'Balkanabat'),
          Container(
            height: 24.0,
            width: 0.5,
            color: Theme.of(context).colorScheme.primary,
          ),
          const SecondaryNavItem(icon: Icons.border_all, text: 'Kategoriýalar'),
        ],
      ),
    );
  }
}

class PrimaryNavItem extends StatelessWidget {
  final String title;
  final String activeItem;
  final void Function(String title)? onTap;

  bool get active => activeItem == title;

  const PrimaryNavItem({
    required this.title,
    required this.activeItem,
    this.onTap,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: const BoxConstraints(minWidth: 100.0),
      child: InkWell(
        onTap: () => onTap?.call(title),
        child: Container(
          padding:
              const EdgeInsets.symmetric(horizontal: 2 * kGap, vertical: kGap),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(kGap),
          ),
          child: Stack(children: [
            if (active)
              Positioned.fill(
                child: LayoutBuilder(
                  builder: (BuildContext context, BoxConstraints constraints) {
                    return OverflowBox(
                      maxWidth: constraints.maxWidth   8 * kGap,
                      maxHeight: constraints.maxHeight   4 * kGap,
                      child: ClipPath(
                          clipper: PrimaryNavItemClipper(),
                          child: Container(color: Colors.white)),
                    );
                  },
                ),
              ),
            Positioned(
              top: 0,
              left: 0,
              child: Image.asset(
                'assets/images/logo.png',
                width: 8 * kGap,
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(top: kGap),
              child: Text(
                title,
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: active
                      ? Theme.of(context).colorScheme.primary
                      : Theme.of(context).colorScheme.secondary,
                ),
              ),
            )
          ]),
        ),
      ),
    );
  }
}

class PrimaryNavItemClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.moveTo(0, size.height);
    path.quadraticBezierTo(
        2 * kGap, size.height, 2 * kGap, size.height - 2 * kGap);
    path.lineTo(size.width - 2 * kGap, size.height - 2 * kGap);
    path.quadraticBezierTo(
        size.width - 2 * kGap, size.height, size.width, size.height);
    path.lineTo(0, size.height);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}

class SecondaryNavItem extends StatelessWidget {
  final IconData icon;
  final String text;

  const SecondaryNavItem({Key? key, required this.icon, required this.text})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Icon(icon),
        Text(text),
      ],
    );
  }
}

Happy coding!

  • Related