100 lines of code to realize Flutter custom TabBar

Flutter is really powerful, but the fly in the ointment is that the ecology needs to be improved. There is no universal basic UI library like Antd or Element in the front end.

The direct impact of this is that the development efficiency can not be improved, and it needs to spend a lot of time and energy on the packaging of basic components.

The official TabBar doesn't meet the demand and doesn't have a suitable wheel, so we have to make our own wheel. Next, I'll take you step by step to implement the custom TabBar

1, Objectives and effects

The demand objectives are:

  1. This page does not need the unified return key and Title on the left side of material
  2. There is a cancel button on the right. Click Cancel to return
  3. Click Tab to switch content with animation effect
  4. You can also toggle Tab by sliding the content area

The effect is as follows:

2, Realization idea

Divide the whole page into two parts, the Tab button above and the content area below.

In order to maintain generality, both the upper Tab and the lower content area need to be passed in by the caller. They are all Widget arrays

class STab extends StatefulWidget {
  // tab set
  final List<Widget> tabs;
  // Page collection
  final List<Widget> pages;

  STab({this.tabs, this.pages});

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

The overall layout of the page is a Column, with the Tab area above and the Content area below wrapped with Expand to achieve the effect of covering the whole screen.

The layout of the outer tab is always the top button of the Stack, and the layout of the tab cannot be cancelled. Multiple tab buttons are arranged with a horizontal layout Row, and the center alignment is set.

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Column(
      children: [
        TabLayout(widget.tabs, selectedIndex, onTabChange, onCancelClick),
        ContentLayout(widget.pages, swipeControl, onPageChange)
      ],
    ));
  }

In the following content area, a third-party library is used to realize the effect of left-right sliding switching flutter_swiper . When you click tab, set the subscript of the swiper to switch the displayed content; When the swiper slides left and right, set the selected status of the tab to achieve the linkage between the selected status of the tab and the sliding of the swiper.

3, Component encapsulation

///tab switch component
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';

class STab extends StatefulWidget {
  // tab set
  final List<Widget> tabs;

  // Page collection
  final List<Widget> pages;

  STab({this.tabs, this.pages});

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

class _STabState extends State<STab> {
  int selectedIndex = 0;
  SwiperController swipeControl = new SwiperController();

  // tab index change callback
  void onTabChange(index) {
    setState(() {
      selectedIndex = index;
    });
    swipeControl.move(index);
  }

  void onCancelClick() {
    print('cancel');
  }

  void onPageChange(index) {
    setState(() {
      selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Column(
      children: [
        TabLayout(widget.tabs, selectedIndex, onTabChange, onCancelClick),
        ContentLayout(widget.pages, swipeControl, onPageChange)
      ],
    ));
  }
}

///Tab layout above
Widget TabLayout(tabs, selectedIndex, onTabChange, onRightButtonClick) {
  List<Widget> getItem() {
    List<Widget> children = [];
    for (var i = 0; i < tabs.length; i++) {
      children.add(
        GestureDetector(
            onTap: () {
              onTabChange(i);
            },
            child: Container(
              padding: EdgeInsets.only(left: 20, right: 20, bottom: 10),
              decoration: BoxDecoration(
                  border: Border(
                      bottom: BorderSide(
                          color: selectedIndex == i
                              ? Color(0xff595959)
                              : Colors.transparent,
                          width: 3))),
              child: tabs[i],
            )),
      );
    }
    return children;
  }

  return Stack(
    children: [
      Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: getItem(),
      ),
      Positioned(
          top: 0,
          right: 0,
          child: GestureDetector(
            child: Container(
              height: 40,
              padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
              child: Text(
                'cancel',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
            onTap: () {
              onRightButtonClick();
            },
          ))
    ],
  );
}

///The following page content layout
Widget ContentLayout(pages, swipeControl, onIndexChanged) {
  return Expanded(
    child: Container(
        decoration: BoxDecoration(color: Colors.white),
        child: Swiper(
          itemCount: pages.length,
          itemBuilder: (BuildContext context, int index) {
            return pages[index];
          },
          loop: false,
          onIndexChanged: (index) {
            onIndexChanged(index);
          },
          controller: swipeControl,
        )),
  );
}

4, How to use

Just pass in tabs and pages

class Demo extends StatelessWidget {

  final List<Widget> tabBodies = [
    ExpensePage(),
    IncomePage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
          padding: EdgeInsets.only(top: 30),
          decoration: BoxDecoration(
              color: Color(0xffF9DC62)
          ),
          child: STab(
            tabs: [
              Text('expenditure', style: TextStyle(fontSize: 18, color: Colors.black),),
              Text('income', style: TextStyle(fontSize: 18, color: Colors.black)),
            ],
            pages: tabBodies,
          ),
      ),
    );
  }
}

5, Conclusion

The encapsulation of components is simply based on the business, without considering more situations. For example, the Cancel button on the right should also be imported from the outside, the color should also be imported from the outside, and whether the incoming data is verified to be legal... You can adjust the source code according to your actual business needs.

As for the degree of packaging, it is good to be suitable and sufficient.

If it's just for your own business, it's enough to encapsulate it. You only need to consider the scenarios in the business. If you want to open source and share it with others, you'd better give higher customization ability.

Tags: Front-end Flutter

Posted by yoda69 on Sat, 14 May 2022 13:01:24 +0300