Flutter∶ 天命人,来,送你个轮椅键盘!
点击关注公众号,“技术干货”及时达!序章?“天命人,你终于来了。”
??“你是谁?”天命人警觉地问道。
??老猴子微微一笑:“我?只是个引路的老家伙罢了。至于你,是被选中的天命人,我将送你能够随心所欲定制的轮椅键盘,助你修行,随我来吧。”
??老猴子熟练地打开了 p 站,天命人终于见到了传说中的能够随心所欲定制的轮椅键盘。它通体晶莹剔透,每一颗键都散发出独特的光芒,似乎可以感知使用者的意念。天命人轻轻一触,键盘立刻根据他的想法自动调整布局,符号和快捷键随着他的念头变幻莫测。
??天命人疑惑: 此物如此厉害,不会还需要「解压」吧。
?
?老猴子笑到:“不不不,你只需要运行 flutter pub add extended_keyboard, 或者直接手动添加 extended_keyboard 到 pubspec.yaml 中的 dependencies 即可。”
??天命人是懂非懂,终于忍不住问道:“老猴子,你为什么只引导我,却不告诉我如何做?”
??老猴子笑了笑,缓缓离去,声音在风中飘来:“天命人,你的路还很长。前方的文档,需要你自己去读。”
?第一章(轮椅原理)想在系统键盘和自定义键盘之间丝滑的切换,一个比较重要的点是,我们需要知道系统键盘的状态,让切换成自定义键盘的时候,动画不那么突兀。
系统键盘状态在 Flutter 中想知道关于键盘的相关信息,我们必须先了解一下WidgetsBinding.instance.window( 在最近的 Flutter 版本中该 api 已经废弃,主要是为了适配多窗口。 后续正式删掉之后,再适配新的 api )。
它代表了 Flutter 应用运行的窗口。通过这个window对象,你可以访问到与窗口相关的各种属性和方法,比如窗口的尺寸、状态栏样式、导航栏样式等,以及执行一些与窗口相关的操作,比如设置窗口的标题、监听窗口的焦点变化等。
viewInsets///视图的填充区域,表示它与所在屏幕的[Screen.viewInsets]相交的部分。
///
///例如,如果视图不与[ScreenConfiguration.viewInsets]区域重叠,[viewInsets]
///将为[WindowPadding.zero]。
///
///该视图矩形每一边的物理像素数量,应用程序可以在这些区域内绘制内容,
///但操作系统可能会在这些区域上放置系统UI,例如键盘或系统菜单,
///从而完全遮挡内容。
finalWindowPaddingviewInsets;
window 的属性 viewInsets,这部分表明,系统可能在这部分区域放置系统 ui,比如键盘或者系统菜单,会挡住应用的内容。
WidgetsBinding.instance.window.viewInsets.bottom 即表明键盘的实时高度,当然,我们还需要除以设备像素与逻辑像素比例。
即键盘的实时高度等于 WidgetsBinding.instance.window.viewInsets.bottom / WidgetsBinding.instance.window.devicePixelRatio。
viewPadding///视图的填充区域,表示它与所在屏幕的[ScreenConfiguration.viewPadding]相交的部分。
///
///例如,如果视图不与[ScreenConfiguration.viewPadding]区域重叠,[viewPadding]
///将为[WindowPadding.zero]。
///
///该屏幕矩形每一边的物理像素数量,应用程序可以在这些区域内放置视图,
///但这些区域可能会被系统UI(如系统通知栏)部分遮挡,或者被显示器的物理
///侵入(例如电视屏幕的过扫描区域或手机传感器外壳)遮挡。
finalWindowPaddingviewPadding;
另外一个影响键盘布局的是 window 的属性 viewPadding,这个就是我们平时说的安全距离。
手机的底部安全距离等于 WidgetsBinding.instance.window.viewPadding.bottom / WidgetsBinding.instance.window.devicePixelRatio。
padding///视图的填充区域,表示它与所在屏幕的[ScreenConfiguration.padding]相交的部分。
///
///例如,如果视图不与[ScreenConfiguration.padding]区域重叠,[padding]
///将为[WindowPadding.zero]。
///
///该屏幕矩形每一边的物理像素数量,应用程序可以在这些区域内放置视图,
///但这些区域可能会被系统UI(如系统通知栏)部分遮挡,或者被显示器的物理
///侵入(例如电视屏幕的过扫描区域或手机传感器外壳)遮挡。
finalWindowPaddingpadding;
看描述,似乎很难区分 viewPadding 和 padding 的区别,我们这里暂时按下不表,我们试着调试一下它们的值。
调试在 iphone 模拟器中,我们尝试打印如下值:
keyboardHeight: MediaQuery.of(context).viewInsets.bottomviewPadding: MediaQuery.of(context).viewPadding.bottompadding: MediaQuery.of(context).padding.bottom日志统一删掉了一些不影响结果的线性过程。
键盘打开flutter: keyboardHeight:0.0------viewPadding:34.0-----padding:34.0
flutter: keyboardHeight:0.03791859337070491------viewPadding:34.0-----padding:33.962081406629295
flutter: keyboardHeight:0.3559106355533004------viewPadding:34.0-----padding:33.6440893644467
flutter: keyboardHeight:16.945799253880978------viewPadding:34.0-----padding:17.054200746119022
flutter: keyboardHeight:48.595437318086624------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:86.29385042190552------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:124.68643552064896------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:160.79388678073883------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:193.20682454109192------viewPadding:34.0-----padding:0.0
...
flutter: keyboardHeight:345.5162001848221------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:346.0------viewPadding:34.0-----padding:0.0
键盘收起flutter: keyboardHeight:346.0------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:345.62914845510386------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:329.01456036418676------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:297.3947924375534------viewPadding:34.0-----padding:0.0
...
flutter: keyboardHeight:64.36210083961487------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:51.02358794212341------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:40.236825704574585------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:31.612241744995117------viewPadding:34.0-----padding:2.387758255004883
flutter: keyboardHeight:24.739083647727966------viewPadding:34.0-----padding:9.260916352272034
flutter: keyboardHeight:19.29748547077179------viewPadding:34.0-----padding:14.70251452922821
flutter: keyboardHeight:15.003506898880005------viewPadding:34.0-----padding:18.996493101119995
...
flutter: keyboardHeight:0.4856146574020386------viewPadding:34.0-----padding:33.51438534259796
flutter: keyboardHeight:0.0------viewPadding:34.0-----padding:34.0
小结键盘打开
viewInsets.bottom 从 0 到 346viewPadding.bottom 从 34 到 34padding.bottom 从 34 到 0
键盘关闭
viewInsets.bottom 从 346 到 0viewPadding.bottom 从 34 到 34padding.bottom 从 0 到 34
可以看出来,viewPadding.bottom 似乎是一个定值,而 padding.bottom 会根据键盘是否开启做变化。
SafeArea在实际项目中,我们不会直接从 WidgetsBinding.instance.window 中直接获取相关信息,而是通过 context 获取,这样的好处是,当 WidgetsBinding.instance.window 中的这些值发生改变的时候,context 所在的组件会自动重新 build 。
MediaQuery.of(context).viewInsets.bottomMediaQuery.of(context).viewPadding.bottomMediaQuery.of(context).padding.bottom一般 App 设计都不希望 App 的内容绘制到安全距离的部分,我们通常会使用 SafeArea 组件,并且将 bottom 设置成 true。
当我们的页面套上一层 SafeArea 组件,并且将 bottom 设置成 true。 在 iphone 模拟器中,我们尝试打印如下值:
keyboardHeight: MediaQuery.of(context).viewInsets.bottom
viewPadding: MediaQuery.of(context).viewPadding.bottom
padding: MediaQuery.of(context).padding.bottom
Widgetbuild(BuildContextcontext){
returnScaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:SafeArea(
bottom:true,
child:Column(
children:WidGET@[
Text(
'(viewInsets):${MediaQuery.of(context).viewInsets.bottom}',
),
Text(
'(viewPadding):${MediaQuery.of(context).viewPadding.bottom}',
),
Text(
'(padding):${MediaQuery.of(context).padding.bottom}',
),
constSpacer(),
constTextField(
decoration:InputDecoration(
hintText:'请输入内容',
),
),
],
),
),
}
键盘打开flutter: keyboardHeight:0.0------viewPadding:0.0-----padding:0.0
flutter: keyboardHeight:0.7043539783917367------viewPadding:0.7043539783917367-----padding:0.0
flutter: keyboardHeight:19.02943018078804------viewPadding:19.02943018078804-----padding:0.0
...
flutter: keyboardHeight:345.5308014154434------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:346.0------viewPadding:34.0-----padding:0.0
键盘收起flutter: keyboardHeight:346------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:343.4928252743557------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:321.89208702743053------viewPadding:34.0-----padding:0.0
...
flutter: keyboardHeight:37.85125684738159------viewPadding:34.0-----padding:0.0
flutter: keyboardHeight:29.701666593551636------viewPadding:29.701666593551636-----padding:0.0
flutter: keyboardHeight:23.22051441669464------viewPadding:23.22051441669464-----padding:0.0
...
flutter: keyboardHeight:0.4466574192047119------viewPadding:0.4466574192047119-----padding:0.0
flutter: keyboardHeight:0.0------viewPadding:0.0-----padding:0.0
小结键盘打开
MediaQuery.of(context).viewInsets.bottom 从 0 到 346MediaQuery.of(context).viewPadding.bottom 从 0 到 34MediaQuery.of(context).padding.bottom 永远为 0
键盘关闭
MediaQuery.of(context).viewInsets.bottom 从 346 到 0MediaQuery.of(context).viewPadding.bottom 从 34 到 0MediaQuery.of(context).padding.bottom 永远为 0
跟没有嵌套 SafeArea 的时候相比,viewPadding 和 padding 的值变化的很奇怪。那为什么会这样呢? 我们来一起看看 SafeArea 的实现方式。
SafeArea 的 build 方法体如下:
通过 context 获取了上一层的 MediaQuery根据 maintainBottomViewPadding 属性判断 padding 是从上一层的 padding 还是 viewPadding 中获取 bottom 。(该属性设置为 true 的实际效果为, 键盘弹起来的时候, Flutter 绘制的部分,跟键盘之间依然有安全距离。 根据之前调试的数据来看,确实如此)包装一个 Padding 来承接用户的设置,并且包裹一层 MediaQuery.removePadding(看名字像是移除 padding)为什么要移除掉 padding, 因为这个组件已经通过 Padding 组件承接了安全距离,如果不移除,下一层组件中,如果又有人通过 MediaQuery.of(context) 或者SafeArea 去设置的话,就会造成重复。
@override
Widgetbuild(BuildContextcontext){
assert(debugCheckHasMediaQuery(context));
finalMediaQueryDatadata=MediaQuery.of(context);
EdgeInsetspadding=data.padding;
//Bottompaddinghasbeenconsumed-i.e.bythekeyboard
if(maintainBottomViewPadding)
padding=padding.copyWith(bottom:data.viewPadding.bottom);
returnPadding(
padding:EdgeInsets.only(
left:math.max(left?padding.left:0.0,minimum.left),
top:math.max(top?padding.top:0.0,minimum.top),
right:math.max(right?padding.right:0.0,minimum.right),
bottom:math.max(bottom?padding.bottom:0.0,minimum.bottom),
),
child:MediaQuery.removePadding(
context:context,
removeLeft:left,
removeTop:top,
removeRight:right,
removeBottom:bottom,
child:child,
),
}
接下来我们看看为什么数值是这样变化的呢?
查看 MediaQuery.removePadding 实现如下:
1.removeBottom 为 true 的时候,padding 的 bottom 值直接设置为了 0 .
2. viewPadding 则等于 removeBottom ? math.max(0.0, viewPadding.bottom - padding.bottom
MediaQueryDataremovePadding({
boolremoveLeft=false,
boolremoveTop=false,
boolremoveRight=false,
boolremoveBottom=false,
}){
if(!(removeLeft||removeTop||removeRight||removeBottom))
returnthis;
returnMediaQueryData(
size:size,
devicePixelRatio:devicePixelRatio,
textScaleFactor:textScaleFactor,
platformBrightness:platformBrightness,
padding:padding.copyWith(
left:removeLeft?0.0:null,
top:removeTop?0.0:null,
right:removeRight?0.0:null,
bottom:removeBottom?0.0:null,
),
viewPadding:viewPadding.copyWith(
left:removeLeft?math.max(0.0,viewPadding.left-padding.left):null,
top:removeTop?math.max(0.0,viewPadding.top-padding.top):null,
right:removeRight?math.max(0.0,viewPadding.right-padding.right):null,
bottom:removeBottom?math.max(0.0,viewPadding.bottom-padding.bottom):null,
),
viewInsets:viewInsets,
alwaysUse24HourFormat:alwaysUse24HourFormat,
highContrast:highContrast,
disableAnimations:disableAnimations,
invertColors:invertColors,
accessibleNavigation:accessibleNavigation,
boldText:boldText,
gestureSettings:gestureSettings,
displayFeatures:displayFeatures,
}
综合上述日志和代码:
总结:
键盘打开
MediaQuery.of(context).viewInsets.bottom 从 0 到 346MediaQuery.of(context).viewPadding.bottom 从 34 - 34 到34 - 0 即 0 到 34MediaQuery.of(context).padding.bottom 永远为 0
键盘关闭
MediaQuery.of(context).viewInsets.bottom 从 346 到 0MediaQuery.of(context).viewPadding.bottom 从 34 - 0 到34 - 34 即 34 到 0MediaQuery.of(context).padding.bottom 永远为 0
总结通过上述的日志分析和代码分析,可以得出,系统键盘的高度包含了安全距离,并且键盘开关也会影响安全距离的值。
那么我们的自定义键盘如果需要等系统键盘一样高,那么自定义键盘的高度等于多少呢?
可能有人会说,这不是废话吗? 不就是等于系统键盘的高度 MediaQuery.of(context).viewInsets.bottom 吗?
但是考虑到 MediaQuery.of(context).viewPadding.bottom 值的变化,我们最终得出公式应该是:
自定义键盘高度=系统键盘高度-固定的安全距离高度+变化的安全距离高度
doublecustomSystemHeight=MediaQuery.of(context).viewInsets.bottom
-(WidgetsBinding.instance.window.viewInsets.bottom/
WidgetsBinding.instance.window.devicePixelRatio)
+MediaQuery.of(context).viewPadding.bottom;
系统键盘开和关当系统键盘开与关的时候,Flutter 框架内部做了什么事情呢?其实在 Flutter 如何优雅地阻止系统键盘弹出 一文中,我们已经初探一二。
SystemChannels.textInput 是掌握 Flutter 跟原生代码通信的通道。packages/flutter/lib/src/services/text_input.dart 这个文件中包含了对其处理的部分。
TextInput._(){
_channel=SystemChannels.textInput;
_channel.setMethodCallHandler(_loudlyHandleTextInputInvocation);
}
里面的方法很多,我们主要应该关心的是下面几个方法。
TextInput.setClient当输入框获得焦点的时候,输入框最终会调用这个方法,将输入框的一些配置传递给原生,并且建立连接。
voidattach(TextInputClientclient,TextInputConfigurationconfiguration){
_channel.invokeMethodvoid(
'TextInput.setClient',
Object[
TextInput._instance._currentConnection!._id,
_configurationToJson(configuration),
],
}
TextInputConfiguration 是输入框的配置,我们可以利用它,来区分该输入框是否是一个我们自定义的键盘,比如我们可以利用 TextInputType, 创建一个不同于系统的 TextInputType。
constTextInputConfiguration({
this.inputType=TextInputType.text,
this.readOnly=false,
this.obscureText=false,
this.autocorrect=true,
SmartDashesType?smartDashesType,
SmartQuotesType?smartQuotesType,
this.enableSuggestions=true,
this.enableInteractiveSelection=true,
this.actionLabel,
this.inputAction=TextInputAction.done,
this.keyboardAppearance=Brightness.light,
this.textCapitalization=TextCapitalization.none,
this.autofillConfiguration=AutofillConfiguration.disabled,
this.enableIMEPersonalizedLearning=true,
this.enableDeltaModel=false,
})
TextInput.clearClient@override
voiddetach(TextInputClientclient){
_channel.invokeMethodvoid('TextInput.clearClient');
}
TextInput.show@override
voidshow(){
_channel.invokeMethodvoid('TextInput.show');
}
TextInput.hide@override
voidhide(){
_channel.invokeMethodvoid('TextInput.hide');
}
第二章(轮椅设计)在分析完毕一些必要的原理之后,我们来做一些设计工作。首先是我们需要一个什么样的组件或者说 api 。
希望能够尽量简单易用尽量不要引入原生的代码(即希望是一个纯 Flutter api),这样如果有新平台,也无需做平台适配。确保系统键盘和自定义键盘之间切换丝滑不突兀基于上面的需求和原理,这里准备了 2 套轮椅来适应不同的场景。
SystemKeyboard系统键盘和自定义键盘的切换的关键是,提前知道系统键盘的高度,这样切换的时候,视觉上面不会造成动画突兀感。
SystemKeyboard 是用来存储管理系统键盘高度的,并且会缓存到本地,让下一次打开 App 的时候,直接能拿到不同键盘类型的高度信息。
KeyboardBuilder如果我们想要关闭系统键盘,并且保持输入框的不丢失焦点,我们没法再使用 SystemChannels.textInput.invokeMethodvoid('TextInput.hide') 了. 相关问题 https://github.com/flutter/flutter/issues/16863
下面的代码是一种变通方案
TextField(
showCursor:true,
readOnly:true,
)
「该组件适用于在同一个输入框展示不同的键盘效果的场景。」
TextInputScope我们可以通过拦截系统通信来阻止系统键盘的弹出,并且根据 TextInput.setClient 来判断当前是什么自定义键盘的类型,来绘制出来当前的自定义键盘。
由于输入框在键盘当打开的状态下, 动态改变 TextInputType,并不能触发 TextInput.setClient 或者TextInput.updateConfig。即同一个输入框在键盘打开的情况下,你没法通过改变 keyboardType 来改变键盘的样式。
Change keyboardType of TextField not work, if the keyboard is showing · Issue #154154 · flutter/flutter (github.com)
「该组件适用于输入框的 keyboardType(TextInputType) 不会动态发生改变的场景。一个页面上可以有多个自定义键盘的输入框和系统键盘的输入框」
第三章(躺上轮椅)道理大家都懂了,那么怎么使用呢?
安装运行 flutter pub add extended_keyboard, 或者直接手动添加 extended_keyboard 到 pubspec.yaml 中的 dependencies.
dependencies:
extended_keyboard:^latest_version
使用SystemKeyboard用于管理系统键盘的高度并提供处理键盘布局更改的功能。
Futurevoidmain()async{
WidgetsFlutterBinding.ensureInitialized();
awaitSystemKeyboard().init();
runApp(constMyApp());
}
KeyboardBuilder如果我们想要关闭系统键盘,并且保持输入框的不丢失焦点,我们没法再使用 SystemChannels.textInput.invokeMethodvoid('TextInput.hide') 了. 相关问题 https://github.com/flutter/flutter/issues/16863
下面的代码是一种变通方案,KeyboardBuilder 基于这种方式来实现的。
TextField(
showCursor:true,
readOnly:true,
)
KeyboardTypeBuilder用于监听 KeyboardType 改变的组件,并且提供 CustomKeyboardController 来控制自定义键盘的开关。
KeyboardTypeBuilder(
builder:(
BuildContextcontext,
CustomKeyboardControllercontroller,
)=
ToggleButton(
builder:(boolactive)=Icon(
Icons.sentiment_very_satisfied,
color:active?Colors.orange:null,
),
activeChanged:(boolactive){
_keyboardPanelType=KeyboardPanelType.emoji;
if(active){
controller.showCustomKeyboard();
if(!_focusNode.hasFocus){
SchedulerBinding.instance
.addPostFrameCallback((DurationtimeStamp){
_focusNode.requestFocus();
}
}else{
controller.showSystemKeyboard();
}
},
active:controller.isCustom&&
_keyboardPanelType==KeyboardPanelType.emoji,
),
),
CustomKeyboardController用于通知 KeyboardType 改变,并且控制自定义键盘的开关。
KeyboardType : 当前键盘的类型isCustom : 是否是自定义键盘showCustomKeyboard : 打开自定义键盘hideCustomKeyboard : 关闭自定义键盘showSystemKeyboard : 打开系统键盘 (通过将 readOnly 设置成 false)unfocus : 使输入框失去焦点, 并且关闭系统和自定义键盘KeyboardBuilder如果使用 Scaffold,请确保将 Scaffold.resizeToAvoidBottomInset 设置为 false。
使用 KeyboardBuilder 小部件来封装包含输入字段的区域,允许在其 builder 回调中创建自定义键盘布局。builder 函数接收一个名为 systemKeyboardHeight 的参数,该参数表示最后显示的系统键盘的高度。此参数可用于为您的自定义键盘设置适当的高度,从而确保无缝且直观的用户体验。
parameterdescriptiondefaultbuilder一个构建器函数,它根据系统键盘高度返回一个小部件。requiredbodyBuilder一个带 readOnly 参数的组件回调requiredresizeToAvoidBottomInset跟 Scaffold.resizeToAvoidBottomInset 作用一致truecontroller自定义键盘控制器nullreturnScaffold(
resizeToAvoidBottomInset:false,
appBar:AppBar(title:constText('ChatDemo(KeyboardBuilder)')),
body:SafeArea(
bottom:true,
child:KeyboardBuilder(
resizeToAvoidBottomInset:true,
builder:(BuildContextcontext,double?systemKeyboardHeight){
returnContainer();
},
bodyBuilder:(boolreadOnly)=Column(children:WidGET@[
Row(
children:WidGET@[
Expanded(
child:TextField(
readOnly:readOnly,
showCursor:true,
onTap:(){
_customKeyboardController.showSystemKeyboard();
},
),
),
KeyboardTypeBuilder(
builder:(
BuildContextcontext,
CustomKeyboardControllercontroller,
)=
ToggleButton(
builder:(boolactive)=Icon(
Icons.sentiment_very_satisfied,
color:active?Colors.orange:null,
),
activeChanged:(boolactive){
_keyboardPanelType=KeyboardPanelType.emoji;
if(active){
controller.showCustomKeyboard();
if(!_focusNode.hasFocus){
SchedulerBinding.instance
.addPostFrameCallback((DurationtimeStamp){
_focusNode.requestFocus();
}
}else{
controller.showSystemKeyboard();
}
},
active:controller.isCustom&&
_keyboardPanelType==KeyboardPanelType.emoji,
),
),
],
),
]),
),
),
Full Demo
TextInputScopeKeyboardBinding / KeyboardBindingMixin你可以直接使用 KeyboardBinding ,或者将 KeyboardBindingMixin 混入到你的 WidgetsFlutterBinding 中。
Futurevoidmain()async{
KeyboardBinding();
awaitSystemKeyboard().init();
runApp(constMyApp());
}
KeyboardConfiguration这个配置包括键盘应该如何构建,它的动画持续时间,它的名字。
parameterdescriptiondefaultgetKeyboardHeight返回自定义键盘的高度requiredbuilder包含输入框的主体requiredkeyboardName自定义键盘的名字requiredshowDuration自定义键盘打开的时间const Duration(milliseconds: 200)hideDuration自定义键盘隐藏的时间const Duration(milliseconds: 200)resizeToAvoidBottomInset跟 Scaffold.resizeToAvoidBottomInset 一样的意思. 如果它不设置,将和 TextInputScope.resizeToAvoidBottomInset 的值相同nullKeyboardConfiguration(
getKeyboardHeight:(double?systemKeyboardHeight)=
systemKeyboardHeight??346,
builder:(){
returnContainer();
},
keyboardName:'custom_number1',
resizeToAvoidBottomInset:true,
),
TextInputScope如果使用 Scaffold,请确保将 Scaffold.resizeToAvoidBottomInset 设置为 false。
parameterdescriptiondefaultbody包含输入框的主体requiredconfigurations自定义键盘配置requiredkeyboardHeight默认的自定义键盘高度346resizeToAvoidBottomInset跟 Scaffold.resizeToAvoidBottomInset 的意思一样.truelateListKeyboardConfiguration_configurations;
@override
voidinitState(){
super.initState();
_configurations=KeyboardConfiguration[
KeyboardConfiguration(
getKeyboardHeight:(double?systemKeyboardHeight)=
systemKeyboardHeight??346,
builder:(){
returnContainer();
},
keyboardName:'custom_number',
),
}
@override
Widgetbuild(BuildContextcontext){
returnScaffold(
appBar:AppBar(
title:constText('TextInputDemo'),
),
resizeToAvoidBottomInset:false,
body:SafeArea(
bottom:true,
child:TextInputScope(
body:Padding(
padding:constEdgeInsets.symmetric(horizontal:5),
child:Column(
children:WidGET@[
TextField(
keyboardType:_configurations[0].keyboardType,
controller:_controller,
decoration:InputDecoration(
hintText:
'ThekeyboardTypeis${_configurations[0].keyboardType.name}',
),
),
],
),
),
configurations:_configurations,
),
),
}
Full Demo
扩展方法TextEditingController 的扩展方法
void insertText(String text) 在当前位置插入文本void delete() 删除一个字符TextEditingValue deleteText() 删除一个字符并且返回删除之后的值,可以根据自己的情况再处理void performAction(TextInputAction action) 跟 TextInputClient.performAction 一样的作用终章?“天命人,别忘了,Flutter 只是工具,真正的力量依然在你心中。”
??“原来,这一切的力量,从未真正离开过我。”天命人低声自语。
?爱 Flutter,爱糖果,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果。
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线