Flutter - 滚动视图中的表单防遮挡 🗒

欢迎关注微信公众号:FSA全栈行动 👋

系列文章

开源库: flutter_scrollview_observer

  1. Flutter - 获取ListView当前正在显示的Widget信息
  2. Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
  3. Flutter - 快速实现聊天会话列表的效果,完美💯
  4. Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
  5. Flutter - 瀑布流交替播放视频 🎞
  6. Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖

一、概述

最近遇到的一个需求上的优化点,在一个详情页中存在提交反馈信息的表单模块,这里简单的模拟了一下,这是运行后的效果

设计师验收的时候觉得这里有一点需要做下优化,那就是表单视图并不大,提交按钮需要完整的显示出来,讲道理还要去算手动偏移量还是挺麻烦的,但是如果结合我的 LinXunFeng/flutter_scrollview_observer 这个库就完全难不倒我们,咔咔两下就搞定了,优化后的效果如下:

Demo 链接:github.com/LinXunFeng/...

接下来我讲一下具体的实现步骤。

二、布局

布局比较容易,简单过一下

滚动视图与观察

使用 ListView 构建滚动视图

dart 复制代码
ScrollController scrollController = ScrollController();

Widget _buildScrollView() {
  Widget resultWidget = ListView.builder(
    controller: scrollController,
    itemBuilder: (context, index) {
      if (formIndex == index) {
        return _buildForm();
      }
      return _buildImage();
    },
    itemCount: 10,
  );
  ...
  return resultWidget;
}

表单模块

dart 复制代码
FocusNode formFocusNode = FocusNode();
  
Widget _buildForm() {
  Widget resultWidget = Form(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        const Text(
          'Feedback',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        // 输入框
        TextField(
          focusNode: formFocusNode,
        ),
        // 提交按钮
        Container(
          width: double.infinity,
          color: Colors.white,
          alignment: Alignment.center,
          margin: const EdgeInsets.only(top: 10.0),
          child: TextButton(
            child: const Text('Submit'),
            onPressed: () {
              // 使输入框失去焦点
              formFocusNode.unfocus();
            },
          ),
        ),
      ],
    ),
  );
  ...
  return resultWidget;
}

三、实战

由于滚动视图使用的是 ListView,所以我们使用对应 ListViewObserver 将其包裹一层做观察

dart 复制代码
// 将滚动视图的 ScrollController 传给 ListObserverController
ListObserverController observerController = ListObserverController(controller: scrollController);

resultWidget = ListViewObserver(
  controller: observerController,
  // 关闭自动触发功能
  autoTriggerObserveTypes: const [],
  child: _buildScrollView(),
  // 不实现观察回调
  // onObserve: (resultModel) {}
);

注:请留意一下注释掉的 onObserve 回调,下面会做相关的版本更新说明。

dart 复制代码
// 表单模块的下标
final int formIndex = 3;

// 监听输入框的焦点状态
formFocusNode.addListener(handleFormFocus);

...

handleFormFocus() async {
  // 我们只处理获得焦点的情况
  if (!formFocusNode.hasFocus) return;
  // 获得焦点后,等待键盘完全展示出来
  await Future.delayed(const Duration(milliseconds: 600));
  // 触发观察滚动视图
  final result = await observerController.dispatchOnceObserve(
    isForce: true, // 强制观察,不用对比上次的观察结果
    isDependObserveCallback: false, // 不依赖观察回调,这样就可以正常返回观察结果
  );
  // 如果观察不成功则不用继续下去了
  if (!result.isSuccess) return;

  // 根据下标从观察结果中找出表单对应的数据
  final formResultModel =
      result.observeResult?.displayingChildModelList.firstWhere((element) {
    return element.index == formIndex;
  });
  if (formResultModel == null) return;
  
  // 进行滚动,使表单视图底部紧贴键盘显示出来
  observerController.controller?.animateTo(
    // 相减的逻辑在正文内说明
    formResultModel.scrollOffset - formResultModel.trailingMarginToViewport,
    duration: const Duration(milliseconds: 200),
    curve: Curves.ease,
  );
}

一些额外说明:

  • 等待键盘完全展示出来的时长(600 ms)是测试出来的同时适用于iOS和安卓的一个值,如果你有更好的方式,可以留言分享一下 😁
  • 1.16.0 版本之前,如果对应的观察回调(如:onObserve)没有实现的话,内部的观察逻辑就无法进行下去,提前结束。而在 1.16.0 版本之后对 dispatchOnceObserve 方法进行了增强,可以通过对 isDependObserveCallback 设置为 false 避开这个逻辑,并且返回值也改造成了 Future<ListViewOnceObserveNotificationResult>,可直接拿到观察结果,相当方便!
  • scrollOffset 是当前滚动视图的偏移量
  • trailingMarginToViewport 是指当前 itemviewport 的底部距离,即 表单模块视图的底部滚动视图的视窗底部 的间距。
  • 由于上述功能我们是依赖于键盘弹出来的视图高度发生变化的特性,键盘出来时视窗大小受到挤压缩小,所以 Scaffold 这里的 resizeToAvoidBottomInset 属性请不要设置为 false。如果设置了 false,则滚动的偏移量需要手动加上键盘的高度~
dart 复制代码
return Scaffold(
  resizeToAvoidBottomInset: true,
  body: ...,
);

在理解完上面的内容后,我们再来看一下最终偏移量计算的逻辑,即

dart 复制代码
observerController.controller?.animateTo(
  formResultModel.scrollOffset - formResultModel.trailingMarginToViewport,
  duration: const Duration(milliseconds: 200),
  curve: Curves.ease,
);

咱们配图食用,当键盘完全展示出来后,滚动视图的的视窗变小了, 由红色区域变成了蓝色区域,而此时表单模块也完全展示出来了,其底部到视窗之间的间距就是 trailingMarginToViewport,为正数,但是我们需要让它紧贴底部,所以此时滚动视图的偏移量是多了的,需要减去这个 trailingMarginToViewport,这便是最终偏移量计算的逻辑。

四、最后

通过上述示例的讲解,相信你对 scrollview_observer 的使用又更加清楚,如果你也觉得这个库好用,请不吝给个 Star 👍

GitHub: github.com/LinXunFeng/...

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
一只大侠的侠29 分钟前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
renke33644 小时前
Flutter for OpenHarmony:色彩捕手——基于HSL色轮与感知色差的交互式色觉训练系统
flutter
猫头虎4 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端