Flutter - How to make a custom TabBar
Asked Answered
B

6

49

My Image

This is the output that I want. I am still new in flutter so can anyone let me know if there is already a widget for this kind of switch or how should I make one ?? Also, I want the data shown below this button to change if I choose the other button but I guess that's obvious.

Thanks in advance.

Behah answered 8/8, 2020 at 9:58 Comment(0)
B
112

You can use the TabBar widget to achieve this. I added a full example demonstrating how you can create this using the TabBar widget:

CODE

class StackOver extends StatefulWidget {
  @override
  _StackOverState createState() => _StackOverState();
}

class _StackOverState extends State<StackOver>
    with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    _tabController = TabController(length: 2, vsync: this);
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _tabController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Tab bar',
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            // give the tab bar a height [can change hheight to preferred height]
            Container(
              height: 45,
              decoration: BoxDecoration(
                color: Colors.grey[300],
                borderRadius: BorderRadius.circular(
                  25.0,
                ),
              ),
              child: TabBar(
                controller: _tabController,
                // give the indicator a decoration (color and border radius)
                indicator: BoxDecoration(
                  borderRadius: BorderRadius.circular(
                    25.0,
                  ),
                  color: Colors.green,
                ),
                labelColor: Colors.white,
                unselectedLabelColor: Colors.black,
                tabs: [
                  // first tab [you can add an icon using the icon property]
                  Tab(
                    text: 'Place Bid',
                  ),

                  // second tab [you can add an icon using the icon property]
                  Tab(
                    text: 'Buy Now',
                  ),
                ],
              ),
            ),
            // tab bar view here
            Expanded(
              child: TabBarView(
                controller: _tabController,
                children: [
                  // first tab bar view widget 
                  Center(
                    child: Text(
                      'Place Bid',
                      style: TextStyle(
                        fontSize: 25,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),

                  // second tab bar view widget
                  Center(
                    child: Text(
                      'Buy Now',
                      style: TextStyle(
                        fontSize: 25,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

OUTPUT output

Barbate answered 8/8, 2020 at 19:16 Comment(5)
Customizing the indicators and tab works this way but still when I hover or pressed the tabs, the "ink splash" effects still have the rectangular shapes (without rounded corners), as I'm using MaterialApp's scaffolding. Do you have any idea how to resolve this?Alkyl
I have the same problem as above.Houseboat
In this way, how to apply the effect to the indicator?Houseboat
For the custom tabbar. Used that example of the custom Image with icon and text. You can also change the radious of that curve tabbar. techandroidhub.com/custom-tab-bar-in-flutterUnconformity
Thanks for the solution. I have a delay in the tab controller recognising the page change so widget state change is very slow. Do you have a workaround? Or could this be because I am working in debug mode?Ales
V
4

The following is my workaround, which I believe to be the best method.

import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {

  const SettingsScreen({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Settings'),
          bottom: PreferredSize(
            preferredSize: Size.fromHeight(AppBar().preferredSize.height),
            child: Container(
              height: 50,
              padding: const EdgeInsets.symmetric(
                horizontal: 20,
                vertical: 5,
              ),
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(
                    10,
                  ),
                  color: Colors.grey[200],
                ),
                child: TabBar(
                  labelColor: Colors.white,
                  unselectedLabelColor: Colors.black,
                  indicator: BoxDecoration(
                    borderRadius: BorderRadius.circular(
                      10,
                    ),
                    color: Colors.pink,
                  ),
                  tabs: const [
                    Tab(
                      text: 'Basic',
                    ),
                    Tab(
                      text: 'Advanced',
                    )
                  ],
                ),
              ),
            ),
          ),
        ),
        body: const TabBarView(
          children: [
            Center(
              child: Text(
                'Basic Settings',
                style: TextStyle(
                  fontSize: 30,
                ),
              ),
            ),
            Center(
              child: Text(
                'Advanced Settings',
                style: TextStyle(
                  fontSize: 30,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Result

Vitality answered 22/12, 2022 at 18:7 Comment(0)
C
3

You can use also PageView widget.

const double borderRadius = 25.0;

class CustomSwitchState extends StatefulWidget {
  @override
  _CustomSwitchStateState createState() => _CustomSwitchStateState();
}

class _CustomSwitchStateState extends State<CustomSwitchState> with SingleTickerProviderStateMixin {

  PageController _pageController;
  int activePageIndex = 0;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SingleChildScrollView(
          physics: const ClampingScrollPhysics(),
          child: GestureDetector(
            onTap: () {
              FocusScope.of(context).requestFocus(FocusNode());
            },
            child: Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              child: Column(
                mainAxisSize: MainAxisSize.max,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(top: 20.0),
                    child: _menuBar(context),
                  ),
                  Expanded(
                    flex: 2,
                    child: PageView(
                      controller: _pageController,
                      physics: const ClampingScrollPhysics(),
                      onPageChanged: (int i) {
                        FocusScope.of(context).requestFocus(FocusNode());
                        setState(() {
                          activePageIndex = i;
                        });
                      },
                      children: <Widget>[
                        ConstrainedBox(
                          constraints: const BoxConstraints.expand(),
                          child: Center(child: Text("Place Bid"),),
                        ),
                        ConstrainedBox(
                          constraints: const BoxConstraints.expand(),
                          child: Center(child: Text("Buy Now"),),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ));
  }

  Widget _menuBar(BuildContext context) {
    return Container(
      width: 300.0,
      height: 50.0,
      decoration: const BoxDecoration(
        color: Color(0XFFE0E0E0),
        borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
      ),
      child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            Expanded(
              child: InkWell(
                borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
                onTap: _onPlaceBidButtonPress,
                child: Container(
                  width: MediaQuery.of(context).size.width,
                  padding: EdgeInsets.symmetric(vertical: 15),
                  alignment: Alignment.center,
                  decoration: (activePageIndex == 0) ? const BoxDecoration(
                    color: Colors.green,
                    borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
                  ) : null,
                  child: Text(
                    "Place Bid",
                    style: (activePageIndex == 0) ? TextStyle(color: Colors.white) : TextStyle(color: Colors.black),
                  ),
                ),
              ),
            ),
            Expanded(
              child: InkWell(
                borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
                onTap: _onBuyNowButtonPress,
                child: Container(
                  width: MediaQuery.of(context).size.width,
                  padding: EdgeInsets.symmetric(vertical: 15),
                  alignment: Alignment.center,
                  decoration: (activePageIndex == 1) ? const BoxDecoration(
                    color: Colors.green,
                    borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
                  ) : null,
                  child: Text(
                    "Buy Now",
                    style: (activePageIndex == 1) ? TextStyle(color: Colors.white, fontWeight: FontWeight.bold) : TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
                  ),
                ),
              ),
            ),
          ],
        ),
    );
  }

  void _onPlaceBidButtonPress() {
    _pageController.animateToPage(0,
        duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
  }

  void _onBuyNowButtonPress() {
    _pageController.animateToPage(1,
        duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
  }

}

OUTPUT enter image description here

Culinary answered 13/9, 2021 at 15:59 Comment(0)
T
3

If you want tab layout like this you can use this

Output:

Output Image

import 'package:flutter/material.dart';
import 'package:icons_helper/icons_helper.dart';

class DetailScreen extends StatefulWidget {
  var body;
  String title = "";

  DetailScreen(this.body, this.title);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<DetailScreen> with TickerProviderStateMixin {
  late int _startingTabCount;

  List<Tab> _tabs = <Tab>[];
  List<Widget> _generalWidgets = <Widget>[];
  late TabController _tabController;

  @override
  void initState() {
    _startingTabCount = widget.body["related_modules"].length;
    _tabs = getTabs(_startingTabCount);
    _tabController = getTabController();
    super.initState();
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        bottom: TabBar(
          isScrollable: true,
          tabs: _tabs,
          controller: _tabController,
        ),
        flexibleSpace: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [
                Colors.grey,
                Colors.blue,
              ],
              stops: [0.3, 1.0],
            ),
          ),
        ),
        leading: IconButton(
          icon: Icon(Icons.arrow_back_ios),
          color: Colors.white,
          onPressed: () {
            Navigator.of(context, rootNavigator: true).pop(context);
          },
        ),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.skip_previous),
            color: Colors.white,
            onPressed: () {
              goToPreviousPage();
            },
          ),
          Container(
            margin: EdgeInsets.only(right: 15),
            child: IconButton(
              icon: Icon(Icons.skip_next),
              color: Colors.white,
              onPressed: () {
                goToNextPage();
              },
            ),
          )
        ],
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: TabBarView(
              physics: NeverScrollableScrollPhysics(),
              controller: _tabController,
              children: getWidgets(),
            ),
          ),
        ],
      ),
    );
  }

  TabController getTabController() {
    return TabController(length: _tabs.length, vsync: this)
      ..addListener(_updatePage);
  }

  Tab getTab(int widgetNumber) {
    return Tab(
      icon: Column(
        children: [
          if (widget.body["related_modules"][widgetNumber]["icon"].toString() ==
              "fa-comments-o") ...[
            Icon(
              Icons.comment_outlined,
            ),
          ] else if (widget.body["related_modules"][widgetNumber]["icon"]
                  .toString() ==
              "fa-map-marker") ...[
            Icon(
              Icons.location_on_rounded,
            ),
          ] else if (widget.body["related_modules"][widgetNumber]["icon"]
                  .toString() ==
              "fa-address-card") ...[
            Icon(
              Icons.contact_page_sharp,
            ),
          ] else ...[
            Icon(
              getIconUsingPrefix(
                name: widget.body["related_modules"][widgetNumber]["icon"]
                    .toString()
                    .substring(3),
              ),
            )
          ]
        ],
      ),
      text: widget.body["related_modules"][widgetNumber]["label"].toString(),
    );
  }

  Widget getWidget(int widgetNumber) {
    return Center(
      child: Text("Widget nr: $widgetNumber"),
    );
  }

  List<Tab> getTabs(int count) {
    _tabs.clear();
    for (int i = 0; i < count; i++) {
      _tabs.add(getTab(i));
    }
    return _tabs;
  }

  List<Widget> getWidgets() {
    _generalWidgets.clear();
    for (int i = 0; i < _tabs.length; i++) {
      _generalWidgets.add(getWidget(i));
    }
    return _generalWidgets;
  }

  void _updatePage() {
    setState(() {});
  }

  //Tab helpers

  bool isFirstPage() {
    return _tabController.index == 0;
  }

  bool isLastPage() {
    return _tabController.index == _tabController.length - 1;
  }

  void goToPreviousPage() {
    _tabController.animateTo(_tabController.index - 1);
  }

  void goToNextPage() {
    isLastPage()
        ? showDialog(
            context: context,
            builder: (context) => AlertDialog(
                title: Text("End reached"),
                content: Text("This is the last page.")))
        : _tabController.animateTo(_tabController.index + 1);
  }
}
Talkative answered 28/9, 2021 at 5:1 Comment(0)
N
2

Try out this you have to change some colour and font:-

import 'package:flutter/material.dart';

typedef SwitchOnChange = Function(int);

class CustomSwitch extends StatefulWidget {
  SwitchOnChange onChange;

  CustomSwitch({this.onChange});

  @override
  State<StatefulWidget> createState() {
    return CustomSwitchState();
  }
}

class CustomSwitchState extends State<CustomSwitch>
    with TickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  GlobalKey key = GlobalKey();
  @override
  void initState() {
    Future.delayed(Duration(milliseconds: 100)).then((v) {
      controller = AnimationController(
          vsync: this, duration: Duration(milliseconds: 300));

      tabWidth = key.currentContext.size.width / 2;
      // var width = (media.size.width - (2 * media.padding.left)) / 2;
      animation = Tween<double>(begin: 0, end: tabWidth).animate(controller);

      setState(() {});

      controller.addListener(() {
        setState(() {});
      });
    });
    super.initState();
  }

  var selectedValue = 0;
  double tabWidth = 0;
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        selectedValue == 0 ? this.controller.forward() : controller.reverse();
        selectedValue = selectedValue == 0 ? 1 : 0;
      },
      child: Container(
        key: key,
        height: 44,
        decoration: BoxDecoration(
            color: Colors.grey, borderRadius: BorderRadius.circular(22)),
        child: Stack(
          children: <Widget>[
            Row(
              children: <Widget>[
                Transform.translate(
                  offset: Offset(animation?.value ?? 0, 0),
                  child: Container(
                    height: 44,
                    width: tabWidth,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(22),
                        boxShadow: [
                          BoxShadow(color: Colors.grey, blurRadius: 3),
                        ]),
                  ),
                ),
              ],
            ),
            Center(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(
                    width: tabWidth,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Icon(Icons.directions_walk),
                        SizedBox(width: 5),
                        Text("Place Bid")
                      ],
                    ),
                  ),
                  Container(
                    width: tabWidth,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Icon(Icons.directions_walk),
                        SizedBox(width: 5),
                        Text("Buy now")
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Nedanedda answered 8/8, 2020 at 10:18 Comment(1)
Don't you need to dispose?Empire
D
0

I had to set the indicatorsize property of Tabbar to TabBarIndicatorSize.tab to achieve the desired result. If you don't set this property then by default only the label part of tabbar will be indicated. I am on flutter version 3.16.9

Dariadarian answered 16/2 at 11:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.