How do I integrate GridView inside TabBar in flutter?

Viewed 731

What I want to achieve is to create Gridview of product widget when subCategory tab pressed which is inside category tab. I would like it to be in a maximum optimized way. Below is structure that i'm trying to achieve. enter image description here

sorry for inconvenient design which i have quickly created using paint :)

2 Answers

try this one, not the best or efficient but I think this all you need based on your design, sorry for the bad code structure. you can see the result in this video.

example code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.grey,
        body: Center(
          child: Searchuserinhomepage(),
        ),
      ),
    );
  }
}

class Searchuserinhomepage extends StatefulWidget {
  @override
  _SearchuserinhomepageState createState() => _SearchuserinhomepageState();
}

class _SearchuserinhomepageState extends State<Searchuserinhomepage>
    with TickerProviderStateMixin {
  TabController _categoryController;
  TabController _subCategoryController1;
  TabController _subCategoryController2;
  TabController _subCategoryController0;
  int _activeIndex = 0;
  final List<Map> myProducts =
      List.generate(10, (index) => {"id": index, "name": "Product $index"})
          .toList();

  @override
  void initState() {
    super.initState();
    _categoryController = TabController(length: 3, vsync: this);
    _subCategoryController1 = TabController(length: 3, vsync: this);
    _subCategoryController2 = TabController(length: 3, vsync: this);
    _subCategoryController0 = TabController(length: 3, vsync: this);
  }

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

  @override
  Widget build(BuildContext context) {
    ;
    _categoryController.addListener(() {
      if (_categoryController.indexIsChanging) {
        setState(() {
          _activeIndex = _categoryController.index;
        });
      }
    });
    return Scaffold(
      appBar: AppBar(
        title: Text('Gridview inside TabBar'),
      ),
      body: Column(
        children: [
          Container(
            height: 50,
            color: Colors.yellow,
            child: TabBar(
              controller: _categoryController,
              tabs: [
                tabs(icon: Icons.ac_unit, color: Colors.red),
                tabs(icon: Icons.baby_changing_station, color: Colors.purple),
                tabs(icon: Icons.cached, color: Colors.deepOrange),
              ],
            ),
          ),
          Container(
            height: 50,
            color: Colors.greenAccent,
            margin: const EdgeInsets.only(bottom: 10.0),
            child: TabBarView(
              controller: _categoryController,
              children: <Widget>[
                TabBar(
                  controller: _subCategoryController0,
                  tabs: [
                    tabs(icon: Icons.radio, color: Colors.amber),
                    tabs(icon: Icons.tab, color: Colors.blue),
                    tabs(icon: Icons.umbrella, color: Colors.teal),
                  ],
                ),
                TabBar(
                  controller: _subCategoryController1,
                  tabs: [
                    tabs(icon: Icons.wysiwyg, color: Colors.amber),
                    tabs(icon: Icons.youtube_searched_for, color: Colors.blue),
                    tabs(icon: Icons.zoom_in, color: Colors.teal),
                  ],
                ),
                TabBar(
                  controller: _subCategoryController2,
                  tabs: [
                    tabs(icon: Icons.pages, color: Colors.amber),
                    tabs(icon: Icons.offline_bolt, color: Colors.blue),
                    tabs(icon: Icons.icecream, color: Colors.teal),
                  ],
                ),
              ],
            ),
          ),
          Expanded(
            child: _activeIndex == 0
                ? TabBarView(
                    controller: _subCategoryController0,
                    children: <Widget>[
                      grid(color: Colors.amber),
                      grid(color: Colors.blue),
                      grid(color: Colors.teal),
                    ],
                  )
                : _activeIndex == 1
                    ? TabBarView(
                        controller: _subCategoryController1,
                        children: <Widget>[
                          grid(color: Colors.red),
                          grid(color: Colors.green),
                          grid(color: Colors.blue),
                        ],
                      )
                    : TabBarView(
                        controller: _subCategoryController2,
                        children: <Widget>[
                          grid(color: Colors.purple),
                          grid(color: Colors.cyan),
                          grid(color: Colors.grey),
                        ],
                      ),
          ),
        ],
      ),
    );
  }

  Padding grid({Color color}) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20.0),
      child: GridView.builder(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 20,
          mainAxisSpacing: 20,
        ),
        itemCount: myProducts.length,
        itemBuilder: (BuildContext ctx, index) {
          return Container(
            alignment: Alignment.center,
            child: Text(myProducts[index]["name"]),
            decoration: BoxDecoration(
              color: color,
              borderRadius: BorderRadius.circular(15),
            ),
          );
        },
      ),
    );
  }

  Tab tabs({IconData icon, Color color}) {
    return Tab(
      icon: Icon(icon, color: color),
    );
  }
}

result screenshot:

enter image description here

I have created fully-functional and much more optimized structure of above screen.

Output

enter image description here

Full code. Copy paste and test freely

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

import 'category.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(
      SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.transparent),
    );

    return MaterialApp(
      title: 'Tab Test',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: CategoriesTabPage(),
    );
  }
}

class CategoriesTabPage extends StatefulWidget {
  @override
  _CategoriesTabPageState createState() => _CategoriesTabPageState();
}

class _CategoriesTabPageState extends State<CategoriesTabPage>
    with SingleTickerProviderStateMixin {
  TabController tabController;
  bool showTab = true;

  final List<Category> categories = [
    Category(
      id: 1,
      name: 'Category1',
      subCategories: [
        SubCategory(id: 11, name: 'Subcategory11', products: [
          Product(id: 111, name: 'Product111', image: 'assets/veg.png'),
          Product(id: 112, name: 'Product112', image: 'assets/veg.png'),
          Product(id: 113, name: 'Product113', image: 'assets/veg.png'),
          Product(id: 114, name: 'Product114', image: 'assets/veg.png'),
          Product(id: 115, name: 'Product115', image: 'assets/veg.png'),
        ]),
        SubCategory(id: 12, name: 'Subcategory12', products: [
          Product(id: 121, name: 'Product121', image: 'assets/veg.png'),
          Product(id: 122, name: 'Product122', image: 'assets/veg.png'),
          Product(id: 123, name: 'Product123', image: 'assets/veg.png'),
          Product(id: 124, name: 'Product124', image: 'assets/veg.png'),
          Product(id: 125, name: 'Product125', image: 'assets/veg.png'),
        ]),
        SubCategory(id: 13, name: 'Subcategory13', products: [
          Product(id: 131, name: 'Product131', image: 'assets/veg.png'),
          Product(id: 132, name: 'Product132', image: 'assets/veg.png'),
          Product(id: 133, name: 'Product133', image: 'assets/veg.png'),
          Product(id: 134, name: 'Product134', image: 'assets/veg.png'),
          Product(id: 135, name: 'Product135', image: 'assets/veg.png'),
        ]),
      ],
    ),
    Category(
      id: 2,
      name: 'Category2',
      subCategories: [
        SubCategory(id: 21, name: 'Subcategory21', products: [
          Product(id: 211, name: 'Product211', image: 'assets/veg.png'),
          Product(id: 212, name: 'Product212', image: 'assets/veg.png'),
          Product(id: 213, name: 'Product213', image: 'assets/veg.png'),
          Product(id: 214, name: 'Product214', image: 'assets/veg.png'),
          Product(id: 215, name: 'Product215', image: 'assets/veg.png'),
        ]),
        SubCategory(id: 22, name: 'Subcategory22', products: [
          Product(id: 221, name: 'Product221', image: 'assets/veg.png'),
          Product(id: 222, name: 'Product222', image: 'assets/veg.png'),
          Product(id: 223, name: 'Product223', image: 'assets/veg.png'),
          Product(id: 224, name: 'Product224', image: 'assets/veg.png'),
          Product(id: 225, name: 'Product225', image: 'assets/veg.png'),
        ]),
        SubCategory(id: 23, name: 'Subcategory23', products: [
          Product(id: 231, name: 'Product231', image: 'assets/veg.png'),
          Product(id: 232, name: 'Product232', image: 'assets/veg.png'),
          Product(id: 233, name: 'Product233', image: 'assets/veg.png'),
          Product(id: 234, name: 'Product234', image: 'assets/veg.png'),
          Product(id: 235, name: 'Product235', image: 'assets/veg.png'),
        ]),
      ],
    ),
  ];

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text(
          'Main Products Page with TabController',
          style: TextStyle(fontWeight: FontWeight.w600),
        ),
      ),
      body: DefaultTabController(
        length: 3,
        child: Column(
          children: <Widget>[
            showTab
                ? Container(
                    constraints: BoxConstraints(maxHeight: 150.0),
                    child: Material(
                        color: Colors.blue,
                        child: TabBar(
                          controller: tabController,
                          isScrollable: false,
                          indicatorColor: Color(0xFFFBD30E),
                          indicatorWeight: 3,
                          tabs: categories
                              .map((category) => Tab(text: category.name))
                              .toList(),
                        )),
                  )
                : Container(),
            Expanded(
              child: TabBarView(
                  controller: tabController,
                  children: categories
                      .map(
                        (category) => SubCategoryWidget(
                          subCategories: category.subCategories,
                        ),
                      )
                      .toList()),
            ),
          ],
        ),
      ),
    );
  }
}

class SubCategoryWidget extends StatefulWidget {
  final List<SubCategory> subCategories;

  SubCategoryWidget({Key key, @required this.subCategories}) : super(key: key);

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

class _SubCategoryWidgetState extends State<SubCategoryWidget>
    with SingleTickerProviderStateMixin {
  TabController _tabController;

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

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

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    double itemWidth = (size.width - 5.0 * 2 - 17) / 3;
    double itemHeight = itemWidth + 66;

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(48.0),
        child: Container(
          decoration: BoxDecoration(
            boxShadow: <BoxShadow>[
              BoxShadow(
                color: Color(0xFFdddae5),
                offset: Offset(0.0, 0.0),
                blurRadius: 10,
              ),
            ],
          ),
          child: Container(
            color: Colors.white,
            child: TabBar(
              controller: _tabController,
              isScrollable: true,
              unselectedLabelColor: Colors.blue,
              indicator: BubbleTabIndicator(
                indicatorRadius: 9,
                indicatorColor: Colors.blue,
                padding: EdgeInsets.symmetric(
                  vertical: 2.0,
                  horizontal: -2.0,
                ),
              ),
              tabs: widget.subCategories
                  .map<Widget>((subCategory) => Tab(text: subCategory.name))
                  .toList(),
            ),
          ),
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: widget.subCategories.map<Widget>((subCategory) {
          return GridView.count(
            padding: const EdgeInsets.only(
              left: 10,
              top: 10,
              right: 7,
              bottom: 0,
            ),
            crossAxisCount: 3,
            mainAxisSpacing: 5.0,
            crossAxisSpacing: 5.0,
            childAspectRatio: itemWidth / itemHeight,
            children: subCategory.products.map((product) {
              return Padding(
                padding: const EdgeInsets.only(top: 10.0, left: 0, right: 0),
                child: Column(
                  children: <Widget>[
                    Expanded(
                      child: Container(
                        child:
                            //  ProductWidget(item),
                            ClipRRect(
                          borderRadius: BorderRadius.circular(14.0),
                          child: Image.asset(
                            product.image,
                            fit: BoxFit.cover,
                          ),
                        ),
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.all(
                            Radius.circular(14.0),
                          ),
                          boxShadow: <BoxShadow>[
                            BoxShadow(
                              color: Colors.blue.withOpacity(0.5),
                              blurRadius: 3.0,
                            ),
                          ],
                        ),
                        margin: EdgeInsets.all(5.0),
                        height: size.width / 3,
                        width: size.width / 3,
                      ),
                    ),
                    Container(
                      padding: EdgeInsets.all(3.0),
                      width: size.width / 3,
                      height: 40,
                      child: Text(
                        product.name,
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                        style: TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.w700,
                          color: Colors.grey,
                        ),
                        textAlign: TextAlign.center,
                      ),
                    ),
                  ],
                ),
              );
            }).toList(),
          );
        }).toList(),
      ),
    );
  }
}

class BubbleTabIndicator extends Decoration {
  final double indicatorHeight;
  final Color indicatorColor;
  final double indicatorRadius;
  final EdgeInsetsGeometry padding;
  final EdgeInsetsGeometry insets;
  final TabBarIndicatorSize tabBarIndicatorSize;

  const BubbleTabIndicator({
    this.indicatorHeight: 35.0,
    this.indicatorColor: Colors.greenAccent,
    this.indicatorRadius: 100.0,
    this.tabBarIndicatorSize = TabBarIndicatorSize.label,
    this.padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0),
    this.insets: const EdgeInsets.symmetric(horizontal: 5.0),
  })  : assert(indicatorHeight != null),
        assert(indicatorColor != null),
        assert(indicatorRadius != null),
        assert(padding != null),
        assert(insets != null);

  @override
  Decoration lerpFrom(Decoration a, double t) {
    if (a is BubbleTabIndicator) {
      return new BubbleTabIndicator(
        padding: EdgeInsetsGeometry.lerp(a.padding, padding, t),
        insets: EdgeInsetsGeometry.lerp(a.insets, insets, t),
      );
    }
    return super.lerpFrom(a, t);
  }

  @override
  Decoration lerpTo(Decoration b, double t) {
    if (b is BubbleTabIndicator) {
      return new BubbleTabIndicator(
        padding: EdgeInsetsGeometry.lerp(padding, b.padding, t),
        insets: EdgeInsetsGeometry.lerp(insets, b.insets, t),
      );
    }
    return super.lerpTo(b, t);
  }

  @override
  _BubblePainter createBoxPainter([VoidCallback onChanged]) {
    return new _BubblePainter(this, onChanged);
  }
}

class _BubblePainter extends BoxPainter {
  _BubblePainter(this.decoration, VoidCallback onChanged)
      : assert(decoration != null),
        super(onChanged);

  final BubbleTabIndicator decoration;

  double get indicatorHeight => decoration.indicatorHeight;
  Color get indicatorColor => decoration.indicatorColor;
  double get indicatorRadius => decoration.indicatorRadius;
  EdgeInsetsGeometry get padding => decoration.padding;
  EdgeInsetsGeometry get insets => decoration.insets;
  TabBarIndicatorSize get tabBarIndicatorSize => decoration.tabBarIndicatorSize;

  Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
    assert(rect != null);
    assert(textDirection != null);

    Rect indicator = padding.resolve(textDirection).inflateRect(rect);

    if (tabBarIndicatorSize == TabBarIndicatorSize.tab) {
      indicator = insets.resolve(textDirection).deflateRect(rect);
    }

    return new Rect.fromLTWH(
      indicator.left,
      indicator.top,
      indicator.width,
      indicator.height,
    );
  }

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    assert(configuration != null);
    assert(configuration.size != null);
    final Rect rect = Offset(
            offset.dx, (configuration.size.height / 2) - indicatorHeight / 2) &
        Size(configuration.size.width, indicatorHeight);
    final TextDirection textDirection = configuration.textDirection;
    final Rect indicator = _indicatorRectFor(rect, textDirection);
    final Paint paint = Paint();
    paint.color = indicatorColor;
    paint.style = PaintingStyle.fill;
    canvas.drawRRect(
        RRect.fromRectAndRadius(indicator, Radius.circular(indicatorRadius)),
        paint);
  }
}

class Category {
  int id;
  String name;
  List<SubCategory> subCategories;

  Category({
    this.id,
    this.name,
    this.subCategories,
  });
}

class SubCategory {
  int id;
  String name;
  List<Product> products;

  SubCategory({
    this.id,
    this.name,
    this.products,
  });
}

class Product {
  int id;
  String name;
  String image;

  Product({
    this.id,
    this.name,
    this.image,
  });
}

Related