flutter 将ListView的最后一个元素固定在屏幕底部

cbjzeqam  于 2023-06-24  发布在  Flutter
关注(0)|答案(2)|浏览(175)

我试图实现一个自定义的导航抽屉使用Flutter。我想附加退出选项到抽屉的底部。问题是注销选项上方的元素数量未知(从3到17)。
因此,如果这些小部件占用了抽屉的一半空间,那么注销选项将位于底部,如果它们太多,您必须滚动才能看到它们,那么注销选项将是最后一个。
我也尝试给予前两个选项一个绿色的背景颜色。你会向我推荐哪种widget树?我有一个关于ListView小部件的想法,它把小部件列表作为构造函数中的参数。
因此,我可以解决前两个项目的不同背景颜色。但我仍然不知道如何将注销选项附加到底部。在这种情况下,它在抽屉的底部,但它可能发生,其他选项将大于屏幕大小,在这种情况下,它应该放在整个列表的底部。
编辑:我在问题中添加了一个设计。注销选项称为Odhlášení。在这种情况下,它在抽屉的底部,但它可能发生,其他选项将大于屏幕大小,在这种情况下,它应该放在整个列表的底部。
设计:

xe55xuns

xe55xuns1#

您可以简单地使用ListView来管理“17”导航选项。将此ListView Package 在Column中。ListView将是Column的第一个子节点(第二个子节点),因此,在底部放置的将是您的注销操作。
如果您在ListView中使用透明的小部件(如ListTile)来显示导航选项,您可以简单地将其 Package 在Container中。Container,除了许多其他小部件之外,还允许您使用color属性设置新的背景色。
使用这种方法,小部件树将如下所示:

- Column                 // Column to place your LogutButton always below the ListView
  - ListView             // ListView to wrap all your navigation scrollable
    - Container          // Container for setting the color to green
      - GreenNavigation
    - Container
      - GreenNavigation
    - Navigation
    - Navigation
    - ...
  - LogOutButton
    • Update 1-Sticky LogOutButton:**要实现LogOutButton坚持到ListView的末尾,你需要做两件事:

1.将Expanded替换为Flexible
1.在ListView中设置shrinkWrap: true

    • 更新2-Spaced LogOutButton,直到列表变大:**实现所描述的行为是一个更困难的步骤。您必须检查ListView是否超出屏幕并可滚动。

为了做到这一点,我写了这个简短的片段:

bool isListLarge() {
    return controller.positions.isNotEmpty && physics.shouldAcceptUserOffset(controller.position);
  }

如果ListView超出其限制,则返回true。现在,我们可以根据isListViewLarge的结果刷新视图的状态。下面是一个完整的代码示例。
独立代码示例(更新2:间隔注销按钮直到大列表):

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        drawer: MyDrawer(),
      ),
    );
  }
}

class MyDrawer extends StatefulWidget {
  @override
  _MyDrawerState createState() => _MyDrawerState();
}

class _MyDrawerState extends State<MyDrawer> {
  ScrollController controller = ScrollController();
  ScrollPhysics physics = ScrollPhysics();

  int entries = 4;

  @override
  Widget build(BuildContext context) {
    Widget logout = IconButton(
        icon: Icon(Icons.exit_to_app),
        onPressed: () => {setState(() => entries += 4)});

    List<Widget> navigationEntries = List<int>.generate(entries, (i) => i)
        .map<Widget>((i) => ListTile(
              title: Text(i.toString()),
            ))
        .toList();

    if (this.isListLarge()) {  // if the List is large, add the logout to the scrollable list
      navigationEntries.add(logout);
    }

    return Drawer(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,  // place the logout at the end of the drawer
        children: <Widget>[
          Flexible(
            child: ListView(
              controller: controller,
              physics: physics,
              shrinkWrap: true,
              children: navigationEntries,
            ),
          ),
          this.isListLarge() ? Container() : logout // if the List is small, add the logout at the end of the drawer
        ],
      ),
    );
  }

  bool isListLarge() {
    return controller.positions.isNotEmpty && physics.shouldAcceptUserOffset(controller.position);
  }
}

独立代码示例(更新1:粘滞注销按钮):

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        drawer: MyDrawer(),
      ),
    );
  }
}

class MyDrawer extends StatefulWidget {
  @override
  _MyDrawerState createState() => _MyDrawerState();
}

class _MyDrawerState extends State<MyDrawer> {
  int entries = 4;

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        children: <Widget>[
          Flexible(
            child: ListView(
              shrinkWrap: true,
              children: List<int>.generate(entries, (i) => i)
                  .map((i) => ListTile(
                        title: Text(i.toString()),
                      ))
                  .toList(),
            ),
          ),
          IconButton(
              icon: Icon(Icons.exit_to_app),
              onPressed: () => {setState(() => entries += 4)})
        ],
      ),
    );
  }
}

独立代码示例(旧:粘在底部):

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        drawer: MyDrawer(),
      ),
    );
  }
}

class MyDrawer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        children: <Widget>[
          Expanded(
            child: ListView(
              children: List<int>.generate(40, (i) => i + 1)
                  .map((i) => ListTile(
                        title: Text(i.toString()),
                      ))
                  .toList(),
            ),
          ),
          IconButton(icon: Icon(Icons.exit_to_app), onPressed: () => {})
        ],
      ),
    );
  }
}
oxcyiej7

oxcyiej72#

我知道我的回答可能有点晚了,但这是为其他人寻找另一个简单的意见在同一问题上与一个简单的小部件意味着。

简短回答:

您可以使用Spacer小部件自动填充剩余空间,而无需将小部件放在不同的层上。

长答案

你仍然可以使用一个高度为double.maxFinite的SizedBox(这是我想到的第一件事),但它会将你的小部件抛出视口,并产生renderflex错误,所以在我们的场景中它不是那么好,我们不希望有任何错误。
或者您可以使用Stack小部件并在bottomCenter中对齐注销按钮,但我们需要添加更多的代码来维护。
因此,这些解决方案总是有一个陷阱。

结果

这是我抽屉的完整代码,请随意使用:

注意:有些人可能会觉得在单独的Map中分离名称,小部件和图标有点过分,但这是我的方式,因为当你有更多页面时,维护代码库变得非常痛苦,或者如果你想快速更改名称,图标或小部件,因为样式会碍事,你每次都必须滚动才能找到ListTile。

因此,如果您发现该结构对于您的场景来说有点多余,请随意编辑代码。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class AppDrawer extends StatelessWidget {
  AppDrawer({super.key});
  final Map<String, Widget> pages = {
      "Page 1": const Placeholder(),
      "Page 2": const Placeholder(),
      "Page 3": const Placeholder(),
      "Page 4": const Placeholder(),
      "Page 5": const Placeholder(),
    };
    final Map<String, IconData> icons = {
      "Page 1": Icons.home_filled,
      "Page 2": Icons.edit_document,
      "Page 3": CupertinoIcons.building_2_fill,
      "Page 4": Icons.all_inbox_rounded,
      "Page 5": CupertinoIcons.mail_solid,
    };

  @override
  Widget build(BuildContext context) {
    List<Widget> drawerContent = [
      const UserAccountsDrawerHeader(
        accountName: Text("black-purple"),
        accountEmail: Text("blackpurpledev@gmail.com"),
        currentAccountPicture: CircleAvatar(),
      ),
      ...pages.keys
          .map(
            (page) => ListTile(
              title: Text(page),
              leading: Icon(
                icons[page],
              ),
              onTap: () => Get.to(pages[page]),
            ),
          )
          .toList(),
      const Spacer(),
      ListTile(
        title: const Text("Logout"),
        leading: const Icon(Icons.logout),
        onTap: () {},
      )
    ];
    return Drawer(
      child: Column(children: drawerContent),
    );
  }
}

希望这有帮助:)
我在做这个简单的例子时使用了一些有用的软件包(如果你还没有机会在你的某个应用中使用它们):

  • Responsive Sizer要获得所需的屏幕高度或宽度的精确值(例如:20.h ==屏幕高度的20%/ 13.w ==屏幕宽度的13%)
  • GetX只需键入**Get.to(YourWidget())**即可轻松在页面之间导航

下面是一个快速截图,向你展示我得到的结果。

相关问题