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 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
程序员小寒16 小时前
前端高频面试题之Vue(初、中级篇)
前端·javascript·vue.js
陈辛chenxin16 小时前
软件测试大赛Web测试赛道工程化ai提示词大全
前端·可用性测试·测试覆盖率
沿着路走到底16 小时前
python 判断与循环
java·前端·python
Code知行合壹16 小时前
AJAX和Promise
前端·ajax
大菠萝学姐16 小时前
基于springboot的旅游攻略网站设计与实现
前端·javascript·vue.js·spring boot·后端·spring·旅游
心随雨下16 小时前
TypeScript中extends与implements的区别
前端·javascript·typescript
摇滚侠16 小时前
Vue 项目实战《尚医通》,底部组件拆分与静态搭建,笔记05
前端·vue.js·笔记·vue
双向3316 小时前
CANN训练营实战指南:从算子分析到核函数定义的完整开发流程
前端
caleb_52016 小时前
vue cli的介绍
前端·javascript·vue.js
Swift社区16 小时前
如何监测 Vue + GeoScene 项目中浏览器内存变化并优化性能
前端·javascript·vue.js