Search code examples
flutterlayout

Flutter scrollable layout with dynamic child


I want to create a generic Layout which accepts a child Widget as a parameter, that lays out the content as follows:

I have an AppBar at the Top, a Title (headline), and below that the Content (could be anything). At the bottom, I have a Column with a few buttons. If the content is too big for the screen, all those widgets, except the AppBar, are scrollable. If the content fits the screen, the title and content should be aligned at the top, and the buttons at the bottom. To showcase what I mean, I created a drawing:

It is easy to create to scrollable content functionality. But I struggle with laying out the content so that the buttons are aligned at the bottom, if the content does NOT need to be scrollable. It is important to say that I don't know the height of the content widget or the buttons. They are dynamic and can change their height. Also, the title is optional and can have two different sizes.

What I tried is the following:

import 'package:flutter/material.dart';

class BaseScreen extends StatelessWidget {
  final String? title;
  final bool bigHeader;
  final Widget child;
  final Widget bottomButtons;

  const BaseScreen({
    Key? key,
    required this.child,
    required this.bottomButtons,
    this.bigHeader = true,
    this.title,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final AppBar appBar = AppBar(
      title: Text("AppBar"),
    );
    double minChildHeight = MediaQuery.of(context).size.height -
        MediaQuery.of(context).viewInsets.bottom -
        MediaQuery.of(context).viewInsets.top -
        MediaQuery.of(context).viewPadding.bottom -
        MediaQuery.of(context).viewPadding.top -
        appBar.preferredSize.height;

    if (title != null) {
      minChildHeight -= 20;
      if (bigHeader) {
        minChildHeight -= bigHeaderStyle.fontSize!;
      } else {
        minChildHeight -= smallHeaderStyle.fontSize!;
      }
    }
    final Widget content = Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (title != null)
          Text(
            title!,
            style: bigHeader ? bigHeaderStyle : smallHeaderStyle,
            textAlign: TextAlign.center,
          ),
        if (title != null)
          const SizedBox(
            height: 20,
          ),
        ConstrainedBox(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              child,
              bottomButtons,
            ],
          ),
          constraints: BoxConstraints(
            minHeight: minChildHeight,
          ),
        ),
      ],
    );
    return Scaffold(
      appBar: appBar,
      body: SingleChildScrollView(
        child: content,
      ),
    );
  }

  TextStyle get bigHeaderStyle {
    return TextStyle(fontSize: 20);
  }

  TextStyle get smallHeaderStyle {
    return TextStyle(fontSize: 16);
  }
}

The scrolling effects work perfectly, but the Buttons are not aligned at the bottom. Instead, they are aligned directly below the content. Does anyone know how I can fix this?


Solution

  • enter image description here

    DartPad you can check here

    customscrollview tutorial

    Scaffold(
              // bottomNavigationBar: ,
              appBar: AppBar(
                title: Text(" App Bar title ${widgets.length}"),
              ),
              //============
              body: CustomScrollView(
                slivers: [
                  SliverFillRemaining(
                    hasScrollBody: false,
                    child: Column(
                      // controller: _mycontroller,
                      children: [
                        title,
                        ...contents,
                        
                        // ---------------------This give Expansion and button get down --------
                        Expanded(
                          child: Container(),
                        ),
                        // ---------------------This give Expansion and button get down --------
                        Buttons
                      ],
                    ),
                  )
                ],
              ))
    

    We can Achieve with the help of CustomScrollView widget and Expanded widget.here Expanded widget just expand between the widget

    Sample Code

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(
        MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),
      );
    }
    
    class MyApp extends StatefulWidget {
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      var widgets = [];
    
      var _mycontroller = ScrollController();
    
      @override
      Widget build(BuildContext context) {
        var title = Center(
            child: Text(
          "Scrollable title ${widgets.length}",
          style: TextStyle(fontSize: 30),
        ));
        var contents = [
          ...widgets,
        ];
        var Buttons = Row(
          children: [
            Expanded(
                child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                height: 100,
                child: ElevatedButton(
                  onPressed: () {
                    setState(() {
                      widgets.add(Container(
                        height: 100,
                        child: ListTile(
                          title: Text(widgets.length.toString()),
                          subtitle: Text("Contents BTN1"),
                        ),
                      ));
                    });
                    // _mycontroller.jumpTo(widgets.length * 100);
                  },
                  child: Text("BTN1"),
                ),
              ),
            )),
            Expanded(
                child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                height: 100,
                child: ElevatedButton(
                  onPressed: () {
                    setState(() {
                      if (widgets.length > 0) {
                        widgets.removeLast();
                      }
                    });
                    // _mycontroller.jumpTo(widgets.length * 100);
                  },
                  child: Text("BTN2"),
                ),
              ),
            ))
          ],
        );
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: SafeArea(
            child: Scaffold(
                // bottomNavigationBar: ,
                appBar: AppBar(
                  title: Text(" App Bar title ${widgets.length}"),
                ),
                body: CustomScrollView(
                  slivers: [
                    SliverFillRemaining(
                      hasScrollBody: false,
                      child: Column(
                        // controller: _mycontroller,
                        children: [
                          title,
                          ...contents,
                          Expanded(
                            child: Container(),
                          ),
                          Buttons
                        ],
                      ),
                    )
                  ],
                )),
          ),
        );
      }
    }