Flutter is powerful, but the fly in the wall is that the ecology of Flutter has yet to be perfected. There is no universal base UI library like the front-end Antd or Element.

The direct effect of this is that the development efficiency can not be improved, and a lot of time and energy need to be spent on the packaging of basic components.

The official TabBar didn’t meet demand and didn’t have the right wheels, so it had to build its own. The following steps will take you through the implementation of a custom TabBar…

I. Objectives and effects

The requirements objectives are:

  1. This page should notmaterialUniform return key and Title on the left
  2. There is a cancel button on the right side, click cancel to return
  3. Click Tab to toggle content with animation
  4. Swiping the content area can also toggle tabs

The effect is shown below:

Second, implementation ideas

Divide the entire page into two parts, the Tab button at the top and the content area at the bottom.

To maintain generality, both the Tab above and the content area below need to be passed in by the caller and are arrays of widgets

class STab extends StatefulWidget {
  / / collection TAB
  final List<Widget> tabs;
  // Collection of pages
  final List<Widget> pages;

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

  @override
  _STabState createState() => _STabState();
}
Copy the code

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

In the Tab layout above, the outermost layer is the Stack layout, because the cancel button is always on the far right without affecting the layout of the Tab button. Multiple TAB buttons are arranged with a horizontal layout Row and are centered.

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

For the following content area, a third-party library flutter_swiper is used to achieve the effect of swiping left and right. When clicking Tab, set the subscript of swiper to switch the content displayed. When swiper is swiped left and right, set the TAB selected state so that the TAB selected state is linked to swiper.

Component packaging

/// TAB switching component
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';

class STab extends StatefulWidget {
  / / collection TAB
  final List<Widget> tabs;

  // Collection of pages
  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) {
    returnContainer( child: Column( children: [ TabLayout(widget.tabs, selectedIndex, onTabChange, onCancelClick), ContentLayout(widget.pages, swipeControl, onPageChange) ], )); }}/// The layout of the Tab 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,
        )),
  );
}

Copy the code

Four, 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('spending', style: TextStyle(fontSize: 18, color: Colors.black),),
              Text('income', style: TextStyle(fontSize: 18, color: Colors.black)), ], pages: tabBodies, ), ), ); }}Copy the code

Five, the conclusion

Components are simply encapsulated according to the business, without considering more situations, such as 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 legitimate is not verified…… You can adjust the source code according to your actual business needs.

As for packaging to what degree is good, suitable for, enough good.

If you’re just using it for your own business, that’s all you need to wrap it around, just consider the scenarios within your business. If you’re going to open source and share it with others, you’d better have more customization.