How to achieve sliver in flutter when collapsing content is dynamic

Viewed 622

I have a requirement to develop a screen where there is collapsible content to be achieved using sliver.

However, the height of collapsible content is dynamic and depends on the number of dynamic widgets applicable to the user (some may not have both dynamic widgets, some have one, some have both). These dynamic widgets load as parallel service to backend and not in sequential manner. Otherwise I would have calculated the height one by one.

Help would be appreciated since all examples on internet point to have a fixed header height for slivers

Example image attached of what i am trying to achieve.

enter image description here

2 Answers

Try with the silver appbar and make sure that your toolbarHeight is 0. Here I used just fixed height for a single element and the total height will be changed based on the number of elements or widgets you have.

import 'package:flutter/material.dart';

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

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

class _DynamicAppbarState extends State<DynamicAppbar> {
  //set the height fixed for each widget
  double fixedHeight = 50;

  // replace with coming elements
  List<String> items = [
    "dynamicWidget1",
    "dynamicWidget2",
    "dynamicWidget3",
    "dynamicWidget4",
  ];

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text("My App Bar"),
        ),
        body: DefaultTabController(
          length: 2,
          child: NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget>[
                SliverAppBar(
                  expandedHeight: fixedHeight * items.length,
                  floating: false,
                  pinned: true,
                  snap: false,
                  toolbarHeight: 0,
                  flexibleSpace: FlexibleSpaceBar(
                    background: Column(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: List<Widget>.generate(
                        items.length,
                        (index) {
                          return Container(
                            height: fixedHeight,
                            child: Center(
                                child: Text(
                              items[index],
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                  fontSize: 24, fontWeight: FontWeight.bold),
                            )),
                          );
                        },
                      ),
                    ),
                  ),
                ),
                SliverPersistentHeader(
                  delegate: _SliverAppBarDelegate(
                    TabBar(
                      labelColor: Colors.black87,
                      unselectedLabelColor: Colors.grey,
                      tabs: [
                        Tab(icon: Icon(Icons.info), text: "Tab 1"),
                        Tab(icon: Icon(Icons.lightbulb_outline), text: "Tab 2"),
                      ],
                    ),
                  ),
                  pinned: true,
                ),
              ];
            },
            body: Center(
              child: Text("Sample text"),
            ),
          ),
        ),
      ),
    );
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;

  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new Container(
      child: _tabBar,
    );
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}


Output:

Before and after scrolling

enter image description here

 // this field is used for getting height of widget dynamically.
 // you can set initial value as per your requirement.
 var _cardSize = Size(Get.width, 300);
  // this key is set to the widget of which we want to get size dynamically
  GlobalKey _key = GlobalKey();

 @override
  void initState() {
    super.initState();
    //IMPORTANT---- this will be called once the build() method gets 
    // executed. By then we will have the widget rendered.
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _getSizeOfWidget();
    });
  }

 //Get the size of the widget
  _getSizeOfWidget() {
    try {
        RenderBox _cardBox = _key.currentContext.findRenderObject();
        _cardSize = _cardBox.size;
        if (!mounted) return;
        setState(() {});
    } catch (e) {}
  }

//Set the key to the widget for which you need to set height dynamically 
//IMPORTANT -- set the height value using _cardSize
  Container(key: _key,child : //any child,height : _cardSize.height);

I hope this answers your question

Related