100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 解决Flutter键盘弹起导致与输入框有间距问题(Flutter键盘弹起Scaffold布局流程)解析

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

时间:2023-02-08 23:46:18

相关推荐

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

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

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

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

假设页面如下:

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

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

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

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

Scaffold是个StatefulWidget组件,所以只要查看ScaffoldState中的build函数即可

@overrideWidget 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()

@overrideWidget 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);@overrideState<StatefulWidget> createState() {return _KeyboardScaffoldWidgetState();}}class _KeyboardScaffoldWidgetState extends XyBaseState<KeyboardScaffoldWidget>with WidgetsBindingObserver {///设计稿宽度double screenWidth = 375;double get adapterRatio {return window.physicalSize.width / screenWidth;}@overridevoid initState() {super.initState();WidgetsBinding.instance!.addObserver(this);}@overridevoid didChangeAccessibilityFeatures() {setState(() {});}// METRICS@overridevoid didChangeMetrics() {setState(() {});}@overridevoid didChangeTextScaleFactor() {setState(() {});}// RENDERING@overridevoid didChangePlatformBrightness() {setState(() {});}@overrideWidget build(BuildContext context) {return MediaQuery(data: createMediaQueryData(adapterRatio),child: widget.child,);}@overridevoid dispose() {super.dispose();WidgetsBinding.instance!.removeObserver(this);}//创建适配后的MediaQueryData,解决:键盘顶起输入框有间距问题///只修改viewInsets的devicePixelRatiostatic MediaQueryData createMediaQueryData(double devicePixelRatio) {SingletonFlutterWindow window = WidgetsBinding.instance!.window;return MediaQueryData(///省略部分代码....///只修改viewInsets的devicePixelRatioviewInsets:EdgeInsets.fromWindowPadding(window.viewInsets, devicePixelRatio));}}

然后使用在Scaffold外层就可以解决

return KeyboardScaffoldWidget(child: Scaffold(...),);

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。