Flutter resizeToAvoidBottomInset true 不适用于 Expanded ListView

Flutter resizeToAvoidBottomInset true not working with Expanded ListView

键盘隐藏了我的 ListView (GroupedListView)。我认为这是因为 Expanded 小部件。

我的body:

Column(
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: GroupedListView<dynamic, String>(
              controller: _scrollController,
              keyboardDismissBehavior:
                    ScrollViewKeyboardDismissBehavior.onDrag,
              physics: const BouncingScrollPhysics(
                    parent: AlwaysScrollableScrollPhysics()),
              itemBuilder: (context, message) {
                  return ListTile(
                      title: ChatBubble(message),
                  );
                },
              elements: messages,
              groupBy: (message) => DateFormat('MMMM dd,yyyy')
                    .format(message.timestamp.toDate()),
              groupSeparatorBuilder: (String groupByValue) =>
                    getMiddleChatBubble(context, groupByValue),
              itemComparator: (item1, item2) =>
                    item1.timestamp.compareTo(item2.timestamp),
              useStickyGroupSeparators: false,
              floatingHeader: false,
              order: GroupedListOrder.ASC,
              ),
            ),
          ),
          WriteMessageBox(
              group: group,
              groupId: docs[0].id,
              tokens: [widget.friendToken])
        ],
      );

为什么 resizeToAvoidBottomInset 不工作?

我开了一个issue to the Flutter team

您使用的文本字段似乎隐藏了数据,或者有时它可能会溢出黑色和黄色条纹的边框

最好使用 SingleChildScrollView,滚动方向使用 scrollDirection 和参数 Axis.verticalAxis.horizontal

return SingleChildScrollView(
      scrollDirection: Axis.vertical,
      child :Column(
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: GroupedListView<dynamic, String>(
              controller: _scrollController,
              keyboardDismissBehavior:
                    ScrollViewKeyboardDismissBehavior.onDrag,
              physics: const BouncingScrollPhysics(
                    parent: AlwaysScrollableScrollPhysics()),
              itemBuilder: (context, message) {
                  return ListTile(
                      title: ChatBubble(message),
                  );
                },
              elements: messages,
              groupBy: (message) => DateFormat('MMMM dd,yyyy')
                    .format(message.timestamp.toDate()),
              groupSeparatorBuilder: (String groupByValue) =>
                    getMiddleChatBubble(context, groupByValue),
              itemComparator: (item1, item2) =>
                    item1.timestamp.compareTo(item2.timestamp),
              useStickyGroupSeparators: false,
              floatingHeader: false,
              order: GroupedListOrder.ASC,
              ),
            ),
          ),
          WriteMessageBox(
              group: group,
              groupId: docs[0].id,
              tokens: [widget.friendToken])
        ],
      );


);

请尝试此解决方案。希望它对你有用。谢谢

 Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: GroupedListView<dynamic, String>(
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              controller: _scrollController,
              keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
              physics: const BouncingScrollPhysics(
                  parent: AlwaysScrollableScrollPhysics()),
              itemBuilder: (context, message) {
                return ListTile(
                  title: ChatBubble(message),
                );
              },
              elements: messages,
              groupBy: (message) =>
                  DateFormat('MMMM dd,yyyy').format(message.timestamp.toDate()),
              groupSeparatorBuilder: (String groupByValue) =>
                  getMiddleChatBubble(context, groupByValue),
              itemComparator: (item1, item2) =>
                  item1.timestamp.compareTo(item2.timestamp),
              useStickyGroupSeparators: false,
              floatingHeader: false,
              order: GroupedListOrder.ASC,
            ),
          ),
        ),
        WriteMessageBox(
            group: group, groupId: docs[0].id, tokens: [widget.friendToken])
     

您似乎希望 GroupedListView 从最后一行可见。 WriteMessageBox 被键盘向上推,遮盖了最后的消息。最直接的解决方案是在键盘可见时将列表滚动到底部。也就是说,当 WriteMessageBox 获得焦点时。

在 build() 方法中将 FocusScope 添加到 WriteMessageBox。变成

FocusScope(
  child: Focus(
   child: WriteMessageBox(),
   onFocusChange: (focused) {
    if (focused) {
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    }
  )
)

截图:

代码:

您可以使用 MediaQueryData 获取键盘的高度,然后将 ListView 向上滚动该数字。

创建此 class:

class HandleScrollWidget extends StatefulWidget {
  final BuildContext context;
  final Widget child;
  final ScrollController controller;
  
  HandleScrollWidget(this.context, {required this.controller, required this.child});

  @override
  _HandleScrollWidgetState createState() => _HandleScrollWidgetState();
}

class _HandleScrollWidgetState extends State<HandleScrollWidget> {
  double? _offset;

  @override
  Widget build(BuildContext context) {
    final bottom = MediaQuery.of(widget.context).viewInsets.bottom;
    if (bottom == 0) {
      _offset = null;
    } else if (bottom != 0 && _offset == null) {
      _offset = widget.controller.offset;
    }
    if (bottom > 0) widget.controller.jumpTo(_offset! + bottom);
    return widget.child;
  }
}

用法:

final ScrollController _controller = ScrollController();

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('ListView')),
    body: HandleScrollWidget(
      context,
      controller: _controller,
      child: Column(
        children: [
          Expanded(
            child: ListView.builder(
              controller: _controller,
              itemCount: 100,
              itemBuilder: (_, i) => ListTile(title: Text('Messages #$i')),
            ),
          ),
          TextField(decoration: InputDecoration(hintText: 'Write a message')),
        ],
      ),
    ),
  );
}

简而言之:使用reversed: true

您看到的是预期行为,原因如下:

ListView 在屏幕上的某些内容调整大小时保留其滚动偏移。此偏移量是列表从开头滚动到的像素数。默认情况下,从顶部开始计数,列表向底部增长。

如果使用reversed: true,滚动位置从底部开始计算,所以最底部的位置是0,列表从底部到顶部增长。它有很多好处:

  1. 0的最底部位置在键盘打开时被保留。任何其他职位也是如此。在任何位置,列表似乎都移到了顶部,最后一个可见元素仍然是最后一个可见元素。

  2. 当您从数据库中获取消息时,可以更轻松地对消息进行排序和分页。您只需按日期时间降序排序并附加到列表,无需在将对象列表提供给 ListView 之前反转对象列表。

  3. 它只在没有听众和控制器操作的情况下工作。一般来说,声明式解决方案更可靠。

经验法则是 反转在顶部加载更多项目的分页列表

示例如下:

import 'package:flutter/material.dart';

void main() async {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: ListView.builder(
                itemCount: 30,
                reverse: true,
                itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
              ),
            ),
            const TextField(),
          ],
        ),
      ),
    );
  }
}

至于resizeToAvoidBottomInset,它完成了它的工作。 Scaffold 确实在键盘打开时缩短了。 ListView也是。所以它显示你更少的项目。对于非反向列表,最底部已经消失了。