解决Flutter键盘弹起导致与输入框有间距问题(Flutter键盘弹起Scaffold布局流程)解析

2023-11-20

一、 在项目中遇到了个如下问题:

  • 当页面底部有个输入框,点击弹出键盘时;输入框与键盘之间有一段间距
  • 通过排除,最后找到了问题根源所在;原因是使用了这个屏幕适配框架导致的。此框架通过直接修改FlutterViewConfiguration()的size与devicePixelRatio达到适配的目的

二、要解决这个问题,就需要了解键盘弹起整个页面做了哪些事情来入手了

  • 假设页面如下:
void main() {
  runApp(MyApp());
}

class MyApp extends BaseStatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter',
      home: Scaffold(
        body: Column(
          children: [
            TextField(),
          ],
        ),
      ),
    );
  }
}

  • 那页面的结构就是如下图了

runApp() ——> MaterialApp() ——> Scaffold() ——> _TextField()

三、来看下的Scaffold #build()函数是如何布局的

  • Scaffold是个StatefulWidget组件,所以只要查看ScaffoldState中的build函数即可
@override
Widget build(BuildContext context) {
  ///省略若干代码....
return _ScaffoldScope(
  ///省略若干代码....
  child: Material(
    child: AnimatedBuilder(animation: _floatingActionButtonMoveController, builder: (BuildContext context, Widget? child) {
      return CustomMultiChildLayout(
        children: children,
        delegate: _ScaffoldLayout(
          extendBody: _extendBody,
          extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
          minInsets: minInsets,
		  ///省略若干代码....
        ),
      );
    }),
  ),
}
  • 最终布局是通过CustomMultiChildLayout组件的,这里就只需要关注minInsets这个参数就行了:
  • 那这个参数的值从哪获取的呢?如下代码:
// The minimum insets for contents of the Scaffold to keep visible.
final EdgeInsets minInsets = mediaQuery.padding.copyWith(
  bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0,
);
// The minimum viewPadding for interactive elements positioned by the
// Scaffold to keep within safe interactive areas.
final EdgeInsets minViewPadding = mediaQuery.viewPadding.copyWith(
  bottom: _resizeToAvoidBottomInset &&  mediaQuery.viewInsets.bottom != 0.0 ? 0.0 : null,
);
  • 可以知道minInsets是个EdgeInsets对象,而它的bottom值则是从mediaQuery.viewInsets.bottom中获取的
  • 那么viewInsets这个参数到底是什么意思呢?来看下类中的注释吧
    在这里插入图片描述

大家可以自己通过谷歌翻译查看下,大致意思就是:被系统UI遮挡的部分,当键盘可见时viewInsets.bottom的值对应于键盘的顶部,也就是说键盘高度会等于viewInsets.bottom的值

3.1 这个MediaQueryData相信大家都不陌生了吧,用来获取系统的一些信息数据

  • 通过MediaQuery.of(context)即可拿到数据
MediaQueryData mediaQuery = MediaQuery.of(context)
  • MediaQuery是继承自InheritedWidget组件的,用来达到各子组件中数据共享的目的。那这个组件是在什么时候初始化的呢?

四、MediaQuery组件的初始化

  • 这个就需要到MaterialApp组件中寻找答案了,这同样是个StatefulWidget组件
  • 最终代码位于_MediaQueryFromWindowsState#buld()

MaterialApp ——> _MaterialAppState#build() ——> WidgetsApp ——> _WidgetsAppState#build() ——> _MediaQueryFromWindow() ——> _MediaQueryFromWindowsState#build()

@override
Widget build(BuildContext context) {
  MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
  if (!kReleaseMode) {
    data = data.copyWith(platformBrightness: debugBrightnessOverride);
  }
  return MediaQuery(
    data: data,
    child: widget.child,
  );
}
  • 可以看到是在这里初始化的MediaQuery,而数据是从WidgetsBinding.instance!.window上获取的数据

重点就是这里了:当键盘显示、隐藏时会从window上获取最新的数据(window.viewInsets),然后最终影响Scaffold的布局;一开始说了是由于使用了屏幕适配框架导致的bug,也就是说这个window上的devicePixelRatiosize并没有修改到,这才导致了viewInsets的bottom值计算错误,可以具体看下MediaQueryData的创建

MediaQueryData.fromWindow(ui.SingletonFlutterWindow window)
  : size = window.physicalSize / window.devicePixelRatio,
    devicePixelRatio = window.devicePixelRatio,
    textScaleFactor = window.textScaleFactor,
    platformBrightness = window.platformBrightness,
    padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
    viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
    viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
    systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
    accessibleNavigation = window.accessibilityFeatures.accessibleNavigation,
    invertColors = window.accessibilityFeatures.invertColors,
    disableAnimations = window.accessibilityFeatures.disableAnimations,
    boldText = window.accessibilityFeatures.boldText,
    highContrast = window.accessibilityFeatures.highContrast,
    alwaysUse24HourFormat = window.alwaysUse24HourFormat,
    navigationMode = NavigationMode.traditional;

五、知道了整个流程那怎么解决呢?

  • 最后我们希望Scaffold中拿到的viewInsets是经过适配后的值,那就可以解决这个问题了
  • 最终利用MediaQuery.of(context)从树中向上查找拿到MediaQueryData这个流程来下手
static MediaQueryData of(BuildContext context) {
  assert(context != null);
  assert(debugCheckHasMediaQuery(context));
  return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data;
}

思路就是:在Scaffold组件外层包一层自己的MediaQuery,让它获取的是我们给定的值就可以了,具体代码如下:

  • 自定义个创建MediaQuery的组件,将viewInsets改成我们屏幕适配后的值
class KeyboardScaffoldWidget extends StatefulWidget {
  final Widget child;

  const KeyboardScaffoldWidget({Key? key, required this.child})
      : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _KeyboardScaffoldWidgetState();
  }
}

class _KeyboardScaffoldWidgetState extends XyBaseState<KeyboardScaffoldWidget>
    with WidgetsBindingObserver {
  ///设计稿宽度
  double screenWidth = 375;

  double get adapterRatio {
    return window.physicalSize.width / screenWidth;
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
  }

  @override
  void didChangeAccessibilityFeatures() {
    setState(() {});
  }

  // METRICS
  @override
  void didChangeMetrics() {
    setState(() {});
  }

  @override
  void didChangeTextScaleFactor() {
    setState(() {});
  }

  // RENDERING
  @override
  void didChangePlatformBrightness() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return MediaQuery(
      data: createMediaQueryData(adapterRatio),
      child: widget.child,
    );
  }

  @override
  void dispose() {
    super.dispose();
    WidgetsBinding.instance!.removeObserver(this);
  }

  //创建适配后的MediaQueryData,解决:键盘顶起输入框有间距问题
  ///只修改viewInsets的devicePixelRatio
  static MediaQueryData createMediaQueryData(double devicePixelRatio) {
    SingletonFlutterWindow window = WidgetsBinding.instance!.window;
    return MediaQueryData(
  		///省略部分代码....
        ///只修改viewInsets的devicePixelRatio
        viewInsets:
            EdgeInsets.fromWindowPadding(window.viewInsets, devicePixelRatio));
  }
}

  • 然后使用在Scaffold外层就可以解决
return KeyboardScaffoldWidget(
	child: Scaffold(...),
);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决Flutter键盘弹起导致与输入框有间距问题(Flutter键盘弹起Scaffold布局流程)解析 的相关文章

  • 如何增加颤振中切屑的宽度

    我想增加宽度Chip 我怎样才能实现这个目标 Chip elevation 6 0 backgroundColor Colors white shape RoundedRectangleBorder borderRadius BorderR
  • Flutter中如何在弹出屏幕后调用函数更新值?

    屏幕 1 显示带有添加按钮的项目列表 屏幕 2 用于将新项目添加到列表的表单 屏幕 2 gt gt 屏幕 1 在屏幕 2 中调用 navigator pop 时 如何在屏幕 1 中调用方法 setState 更新列表 谁能帮我吗 我不想再次
  • 如何取消配置 flutterfire?

    我们在 CLI 中使用以下命令将 flutter 应用程序与 firebase 连接 flutterfire configure 如文档中所述 https firebase flutter dev docs overview https f
  • 我想将对象列表添加到 firestore 文档中,-flutter

    我想将对象列表添加到 firestore 文档 我定义了产品数据模型 我还有类别数据模型 我想将类别列表添加到 firestore 中的产品文档中 我将类别添加到临时列表 然后将值放入product categories 产品 类别 类别t
  • 如何根据数据值过滤flutter中的Hive数据?

    在颤振中 我使用ValueListenableBuilder用于获取配置单元数据列表的小部件 我正在尝试按数据值过滤数据 例子 Key 1 name value mydata1 des value mydescription1 value
  • 在 Flutter 中显示 CircularProgressIndicator 在前面

    我想显示一个圆形栏 就像在其他小部件前面加载一样 下面是我当前正在使用的代码 它显示了循环加载 但它位于其他小部件之间 它应该在顶部 根据我尝试使用 Stack 的建议 但它仍然显示在小部件之间 我在这里做错了什么 class LoginP
  • 当 flutter 应用程序终止时,普通推送通知会默默出现或根本不出现

    我在 nodejs 上使用 firebase admin 向用户发送推送通知 https firebase google com docs admin setup https firebase google com docs admin s
  • 带有 ListTiles 和按钮行的 Flutter 下拉菜单

    我正在尝试构建一个自定义下拉菜单 如下所示 我已经成功地实现了ListTiles and Row of Buttons没有下拉菜单 但我不确定如何将所有内容嵌套在下拉菜单类中 这是我到目前为止所得到的 class HomePage exte
  • Flutter 中没有上下文的 AlertDialog

    我想在 http 获取失败时显示 AlertDialog 函数显示对话框 https api flutter dev flutter material showDialog html https api flutter dev flutte
  • Charts_flutter x 轴上的标签/文本相互重叠

    我使用flutter中的charts flutter包渲染条形图 但 x 轴上的域标签相互重叠 我有办法解决这个问题吗 有没有办法倾斜文本或增加图表的宽度 使其可以水平滚动 我尝试过寻找任何解决方案 例如 labelspecs 但无法找到解
  • 在 dart 的 MultipartRequest 中添加授权标头

    我在用多部分请求 https pub dartlang org documentation http latest http MultipartRequest class html在 Dart 中以便将文件上传到 API 但是我需要在我的请
  • Flutter:如何移动文本字段字符计数器?

    标题很好地总结了这个问题 我有一个TextField with a maxLength 250这就是它的样子 有没有办法把柜台放在其他地方 最好位于发送按钮的左侧 但也可能位于发送按钮的上方和左侧TextField 有任何想法吗 谢谢 可能
  • 如何在 Flutter Provider 中删除 StreamController 中的数据?

    我正在使用provider来构建我的应用程序 因此数据被添加到StreamController中 每次刷新我的应用程序时 它都会调用API 然后将数据推送到StreamController 问题是如何在替换之前删除数据新的那一个 contr
  • 如何将额外的文本添加到颤振谷歌地图自定义标记中?

    问题是如何将自定义谷歌地图标记上的文本重叠与代表车辆登记号的文本融合在一起 我尝试使用此方法将文本叠加在图标上 生成器 上下文 gt 但根本不被认可 class MapsDemo extends StatefulWidget overrid
  • flutter 中 sqlite 中的多个参数

    我想知道如何将多个参数传递给 sqllite 中的原始查询 我的代码如下 query async get a reference to the database Database db await DatabaseHelper instan
  • 如何在 flutter 中仅显示列表中的 5 项

    我想在 flutter 中显示一个列表 我正在使用listView 问题是我只想显示 5 个项目 我的意思是当用户向下滚动时我想从开始索引中删除并将另一个小部件添加到包含我的小部件的列表的末尾 但是当我这样做时ScrollView 不会停留
  • flutter:动画过渡到命名路线

    当我使用Navigator pushNamed context someRoute 有一个最小的动画 从屏幕底部沿着新路线滑动 在 Android 上 在 iOS 上可能看起来不同 如何向此过渡添加自定义动画 I found 本文 http
  • 用颤动画布在形状上切一个洞

    如何使用颤动画布在形状上 切一个洞 我有一组相当复杂的形状 看起来像现实世界的物体 该物体上有一个圆角矩形形状的孔 我真的很想从形状中减去 RRect 但我找不到任何有关如何执行此操作的信息 canvas clipRRect myRRect
  • 实施 ChangeNotifier 与 StateNotifier

    我很熟悉Provider https pub dev packages provider打包并将其与ChangeNotifier 假设我有 3 个 getter 和具有不同功能的方法 切换加载 切换图像加载 切换 ObsecurePassw
  • Dart 从 UInt8List 获取扩展

    我正在使用该包图像选择器 https pub dev packages image picker接受来自用户的图像 这会产生 PickedFile 和字节数组 由于图像随后被上传 我想知道如何从字节数组中猜测 mime 类型 PickedF

随机推荐

  • Webpack构建多页应用Mpa(一):阐述设计概要

    应用场景 如果现在要做一个前后端分离的项目 可能第一反应就是使用市面上很火的三大MVVM框架 Vue React Angular 但如果团队没有专职前端 并且项目预留时间也很紧张 没足够时间去系统学习工程化Vue项目 但是却也想让html
  • 代码段中存放数据

    1 前面我们写的程序中 只有一个代码段 我们先来在代码段中使用数据 看看和单独一个数据段存放数据有什么差别 考虑这样一个问题 编程计算以下8个数据的和 结果存放在ax寄存器中 0123H 0456H 0789H 0abcH 0defH 0f
  • Unity3d 插件 系列——DoTweenPro介绍(图文详细+案例)

    Unity3d 插件 系列 DoTweenPro介绍 图文详细 案例 前言 一 DoTweenPro简介 二 DoTweenPro安装 三 DoTweenPro主要组件 1 DoTweenAnimation 2 DoTweenPath 3
  • python3.7在centos下安装pygame

    python编程从入门到实践 一书的第二部分开始 需要安装pygame 我用的是centos7 里面有内置的python2 我自己装了python3 7 pip安装pip3 安装过程网上教程很多 但是一般安装的不完全 需要注意的是安装ssl
  • 关于Yarn的一些个人总结

    文章目录 前言 一 Yarn是什么 二 Yarn由什么组成 三 Yarn用来做什么 四 Yarn的优势是什么 五 Yarn解决了什么问题 总结 前言 在前面我们可以得出Yarn是Hadoop生态圈中一个重要得组成部分 主管资源管理 但是具体
  • 2021赣网杯网络安全大赛_部分Writeup

    目录 Web 1 checkin 2 gwb web easypop 3 gwb web2 挖洞大师 misc 1 decodemaster 2 gwb misc lovemath 3 gwb misc3 testcat Web 1 che
  • C++11智能指针之std::shared_ptr

    std shared ptr是在c 11中引入的一种智能指针 其特点是它所指向的资源具有共享性 即多个shared ptr可以指向同一份资源 在c 中使用shared ptr需要包含
  • Unity中UI框架的使用1-添加面板、显示Loading页面

    其中BasePanel和Canvas都是挂在面板的预制物上的 1 导入我们的UI框架 本篇文章中有用的是两个UIPanelType NUIManager和NBasePanel 会放在文章最后供大家使用 2 先将我们做好的Panel设置成预制
  • 有些变压器的中性点为何要装避雷器?

    有些变压器的中性点为何要装避雷器 答 当变压器的中性点接地运行时 是不需要装避雷器的 但是 由于运行方式的需要 为了防止单相接地事故时短路电流过大 220kV及以下系统中有部分变压器的中性点是断开运行的 在这种情况下 对于中性点绝缘不是按照
  • 6.js--布尔值和null

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 目录 1 布尔数据 boolean 2 null 3 null和undefined的意义 1 布尔数据 boolean var a true var b false
  • CSDN创作说明

    这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题 有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一个表格 设定内容居中 居左 居右 Sma
  • 计算机丢失MSVCR120.dll是什么意思,电脑找不到MSVCR120.dll如何修复?

    我们在玩游戏或者运行一些软件的时候 系统提示无法启动此程序 因为计算机中丢失MSVCR120 dll 尝试重新安装该程序以解决此问题 这个需要怎么修复呢 详细困扰着不少小伙伴 小编今天就把教程分享给大家 方法如下 首先是打开电脑浏览器后在顶
  • Java判断时间是否超过30天,java 字符串时间 判断是否在30天内

    时间是字符串类型时 比较大小的时候 先要 转化成 时间格式 然后在进行比较 字符串比较大小的规则 首先取出两个字符串的长度 比较较小的长度内 两者是否相等 若不相等 则直接返回该位置字符的ASCII码相减后的值 若各位置都相等 则将两个字符
  • 问题 A: 天天向上

    题目描述 假设某同学初始能力值为1 现在该同学学习10天 休息1天 学习一天 能力提升0 001 休息一天 能力下降0 001 问一年 365天 后该同学的能力是多少 输出 365天后该同学的能力是 保留小数点6位 分析 书上76页有类似的
  • STM32 串行FLASH文件系统FatFs

    目录 一 Windows系统为例 二 文件系统的结构与特性 为什么要应用文件分配表 三 FatFs文件系统 1 FatFs 文件系统源码介绍 2 FatFs在程序中的关系网 四 配置FatFs移植程序 1 配置diskio c文件 2 配置
  • MobaXterm x11-forwarding

    Linux系统也是有图像界面的 它的方式和Windows不一样 叫做 X Window 采用的是X11协议 X11 中的 X 指的就是 X 协议 11 指的是采用 X 协议的第 11 个版本 客户端是X server 用MobaXterm登
  • CocosCretor解决premultipliedAlpha黑边问题

    在官方文档中的说明 premultipliedAlpha 对于是否启动贴图预乘 当图片的透明区域出现色块时 需要关闭该选项 当图片的半透明区域颜色变黑时 需要启用该选项 之前在项目中导出的spine有黑边问题 也就是半透明区域颜色变黑 然后
  • 《软件测试的艺术》第五章 模块(单元)测试

    软件测试的艺术 第五章 模块 单元 测试 5 0 前言 5 1 测试用例设计 5 2 增量测试 5 3 自顶向下测试和自底向上测试 5 3 1 自顶向下的测试 5 3 2 自底向上的测试 5 3 3 比较 5 4 执行测试 5 5 小结 参
  • vite-svg-loader,在项目里轻松使用svg,ts项目需特别注意!

    前言 vite svg loader插件可以让我们像使用vue组件那样使用svg图 使用起来超级方便 安装 npm install vite svg loader save dev 使用 1 vite config ts配置 import
  • 解决Flutter键盘弹起导致与输入框有间距问题(Flutter键盘弹起Scaffold布局流程)解析

    一 在项目中遇到了个如下问题 当页面底部有个输入框 点击弹出键盘时 输入框与键盘之间有一段间距 通过排除 最后找到了问题根源所在 原因是使用了这个屏幕适配框架导致的 此框架通过直接修改FlutterViewConfiguration 的si