FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
      • 一、概述
      • 二、布局
        • 滚动视图
        • 表单模块
      • 三、实战
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
  • 移动端
  • Flutter开发
LinXunFeng
2023-08-13
目录

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

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

# 一、概述

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

优化前

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

优化后

Demo 链接:https://github.com/LinXunFeng/flutter_scrollview_observer/blob/main/example/lib/features/scene/scrollview_form_demo/scrollview_form_demo_page.dart (opens new window)

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

# 二、布局

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

# 滚动视图

使用 ListView 构建滚动视图

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

# 表单模块

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 将其包裹一层做观察

// 将滚动视图的 ScrollController 传给 ListObserverController
ListObserverController observerController = ListObserverController(controller: scrollController);

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

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

// 表单模块的下标
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 是指当前 item 到 viewport 的底部距离,即 表单模块视图的底部 到 滚动视图的视窗底部 的间距。
  • 由于上述功能我们是依赖于键盘弹出来的视图高度发生变化的特性,键盘出来时视窗大小受到挤压缩小,所以 Scaffold 这里的 resizeToAvoidBottomInset 属性请不要设置为 false。如果设置了 false,则滚动的偏移量需要手动加上键盘的高度~
return Scaffold(
  resizeToAvoidBottomInset: true,
  body: ...,
);

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

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

图解

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

#Dart#Flutter
Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
Flutter - 秒杀1/2曝光统计 📊

← Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖 Flutter - 秒杀1/2曝光统计 📊→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×