Home > front end >  Flutter scrollable layout with dynamic child
Flutter scrollable layout with dynamic child

Time:02-25

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?

CodePudding user response:

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:

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
                    ],
                  ),
                )
              ],
            )),
      ),
    );
  }
}
  • Related