具有动态 child 的 Flutter 可滚动布局

Flutter scrollable layout with dynamic child

我想创建一个接受 child 小部件作为参数的通用布局,其内容布局如下:

我在顶部有一个 AppBar,一个标题(标题),在其下方是内容(可以是任何内容)。在底部,我有一个带有几个按钮的 Column。如果内容对于屏幕而言太大,所有这些小部件(AppBar 除外)都是可滚动的。如果内容适合屏幕,标题和内容应该在顶部对齐,按钮在底部。 为了展示我的意思,我画了一张图:

创建可滚动内容功能很容易。但是如果内容不需要滚动,我很难布置内容以使按钮在底部对齐。 重要的是要说我不知道​​内容小部件或按钮的高度。它们是动态的,可以改变它们的高度。此外,标题是可选的,可以有两种不同的大小。

我试过的是:

导入'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);
  }
}

滚动效果完美,但按钮未在底部对齐。相反,它们直接在内容下方对齐。有谁知道我该如何解决这个问题?

试试这个:

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);
}

截图:

without_scrolling

scrolled_up

scrolled_down

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
                  ],
                ),
              )
            ],
          ))

我们可以借助CustomScrollView widget 和 Expanded widget.here Expanded widget 只是在widget

之间展开

示例代码

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