3

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?

Apri
  • 1,241
  • 1
  • 8
  • 33

2 Answers2

4

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
                    ],
                  ),
                )
              ],
            )),
      ),
    );
  }
}
lava
  • 6,020
  • 2
  • 31
  • 28
  • The problem here is that I can't set the height of the content widget, as it needs to be flexible. When I don't explicitly set the height, I get this error: `LayoutBuilder does not support returning intrinsic dimensions` – Apri Feb 25 '22 at 14:56
  • could you provide the code – lava Feb 25 '22 at 15:22
  • Nevermind, it was my error. I just had some issues in my layout. It works without setting a specific height. – Apri Feb 25 '22 at 15:58
  • Just noticed that AutoSizeText doesn't work – Apri Feb 25 '22 at 15:58
1

Try this:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BaseScreen(
        bottomButtons: [
          ElevatedButton(onPressed: () {}, child: const Text('Button 1')),
          ElevatedButton(onPressed: () {}, child: const Text('Button 2')),
        ],
        content: Container(
          color: Colors.lightGreen,
          height: 200,
        ),
        title: 'Title',
      ),
    );
  }
}

class BaseScreen extends StatelessWidget {
  final bool bigHeader;
  final List<Widget> bottomButtons;
  final String? title;
  final Widget content;

  const BaseScreen({
    this.bigHeader = true,
    required this.bottomButtons,
    required this.content,
    this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AppBar'),
      ),
      body: CustomScrollView(
        slivers: [
          SliverFillRemaining(
            hasScrollBody: false,
            child: Column(
              children: [
                if (title != null)
                  Padding(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    child: Text(
                      title!,
                      style: bigHeader ? _bigHeaderStyle : _smallHeaderStyle,
                      textAlign: TextAlign.center,
                    ),
                  ),
                content,
                const Spacer(),
                ...bottomButtons,
              ],
            ),
          ),
        ],
      ),
    );
  }

  TextStyle get _bigHeaderStyle => const TextStyle(fontSize: 20);
  
  TextStyle get _smallHeaderStyle => const TextStyle(fontSize: 16);
}

Screenshots:

without_scrolling

scrolled_up

scrolled_down

minhqdao
  • 21
  • 3
  • The problem here is that I can't set the height of the content widget, as it needs to be flexible. When I don't explicitly set the height, I get this error: `LayoutBuilder does not support returning intrinsic dimensions` – Apri Feb 25 '22 at 14:56