序章
“天命人,你终于来了。”
“你是谁?”天命人警觉地问道。
老猴子微微一笑:“我?只是个引路的老家伙罢了。至于你,是被选中的天命人,我将送你能够随心所欲定制的轮椅键盘,助你修行,随我来吧。”
老猴子熟练地打开了 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,例如键盘或系统菜单,
/// 从而完全遮挡内容。
final WindowPadding viewInsets;
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(如系统通知栏)部分遮挡,或者被显示器的物理
/// 侵入(例如电视屏幕的过扫描区域或手机传感器外壳)遮挡。
final WindowPadding viewPadding;
另外一个影响键盘布局的是 window 的属性 viewPadding,这个就是我们平时说的安全距离。
手机的底部安全距离等于 WidgetsBinding.instance.window.viewPadding.bottom / WidgetsBinding.instance.window.devicePixelRatio。
padding
/// 视图的填充区域,表示它与所在屏幕的 [ScreenConfiguration.padding] 相交的部分。
///
/// 例如,如果视图不与 [ScreenConfiguration.padding] 区域重叠,[padding]
/// 将为 [WindowPadding.zero]。
///
/// 该屏幕矩形每一边的物理像素数量,应用程序可以在这些区域内放置视图,
/// 但这些区域可能会被系统 UI(如系统通知栏)部分遮挡,或者被显示器的物理
/// 侵入(例如电视屏幕的过扫描区域或手机传感器外壳)遮挡。
final WindowPadding padding;
看描述,似乎很难区分 viewPadding 和 padding 的区别,我们这里暂时按下不表,我们试着调试一下它们的值。
调试
在 iphone 模拟器中,我们尝试打印如下值:
- keyboardHeight:
MediaQuery.of(context).viewInsets.bottom - viewPadding:
MediaQuery.of(context).viewPadding.bottom - padding:
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
Widget build(BuildContext context) {
return Scaffold(
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}',
),
const Spacer(),
const TextField(
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
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final MediaQueryData data = MediaQuery.of(context);
EdgeInsets padding = data.padding;
// Bottom padding has been consumed - i.e. by the keyboard
if (maintainBottomViewPadding)
padding = padding.copyWith(bottom: data.viewPadding.bottom);
return Padding(
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
MediaQueryData removePadding({
bool removeLeft = false,
bool removeTop = false,
bool removeRight = false,
bool removeBottom = false,
}) {
if (!(removeLeft || removeTop || removeRight || removeBottom))
return this;
return MediaQueryData(
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 值的变化,我们最终得出公式应该是:
自定义键盘高度=系统键盘高度-固定的安全距离高度+变化的安全距离高度
double customSystemHeight = 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
当输入框获得焦点的时候,输入框最终会调用这个方法,将输入框的一些配置传递给原生,并且建立连接。
void attach(TextInputClient client, TextInputConfiguration configuration) {
_channel.invokeMethod<void>(
'TextInput.setClient',
<Object>[
TextInput._instance._currentConnection!._id,
_configurationToJson(configuration),
],
);
}
TextInputConfiguration 是输入框的配置,我们可以利用它,来区分该输入框是否是一个我们自定义的键盘,比如我们可以利用 TextInputType, 创建一个不同于系统的 TextInputType。
const TextInputConfiguration({
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
void detach(TextInputClient client) {
_channel.invokeMethod<void>('TextInput.clearClient');
}
TextInput.show
@override
void show() {
_channel.invokeMethod<void>('TextInput.show');
}
TextInput.hide
@override
void hide() {
_channel.invokeMethod<void>('TextInput.hide');
}
第二章(轮椅设计)
在分析完毕一些必要的原理之后,我们来做一些设计工作。首先是我们需要一个什么样的组件或者说 api 。
- 希望能够尽量简单易用
- 尽量不要引入原生的代码(即希望是一个纯
Flutterapi),这样如果有新平台,也无需做平台适配。 - 确保系统键盘和自定义键盘之间切换丝滑不突兀
基于上面的需求和原理,这里准备了 2 套轮椅来适应不同的场景。
SystemKeyboard
系统键盘和自定义键盘的切换的关键是,提前知道系统键盘的高度,这样切换的时候,视觉上面不会造成动画突兀感。
SystemKeyboard 是用来存储管理系统键盘高度的,并且会缓存到本地,让下一次打开 App 的时候,直接能拿到不同键盘类型的高度信息。
KeyboardBuilder
如果我们想要关闭系统键盘,并且保持输入框的不丢失焦点,我们没法再使用 SystemChannels.textInput.invokeMethod<void>('TextInput.hide') 了. 相关问题 github.com/flutter/flu…
下面的代码是一种变通方案
TextField(
showCursor: true,
readOnly: true,
)
该组件适用于在同一个输入框展示不同的键盘效果的场景。
TextInputScope
我们可以通过拦截系统通信来阻止系统键盘的弹出,并且根据 TextInput.setClient 来判断当前是什么自定义键盘的类型,来绘制出来当前的自定义键盘。
由于输入框在键盘当打开的状态下, 动态改变 TextInputType,并不能触发 TextInput.setClient 或者TextInput.updateConfig。即同一个输入框在键盘打开的情况下,你没法通过改变 keyboardType 来改变键盘的样式。
该组件适用于输入框的 keyboardType(TextInputType) 不会动态发生改变的场景。一个页面上可以有多个自定义键盘的输入框和系统键盘的输入框
第三章(躺上轮椅)
道理大家都懂了,那么怎么使用呢?
安装
运行 flutter pub add extended_keyboard, 或者直接手动添加 extended_keyboard 到 pubspec.yaml 中的 dependencies.
dependencies:
extended_keyboard: ^latest_version
使用
SystemKeyboard
用于管理系统键盘的高度并提供处理键盘布局更改的功能。
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemKeyboard().init();
runApp(const MyApp());
}
KeyboardBuilder
如果我们想要关闭系统键盘,并且保持输入框的不丢失焦点,我们没法再使用 SystemChannels.textInput.invokeMethod<void>('TextInput.hide') 了. 相关问题 github.com/flutter/flu…
下面的代码是一种变通方案,KeyboardBuilder 基于这种方式来实现的。
TextField(
showCursor: true,
readOnly: true,
)
KeyboardTypeBuilder
用于监听 KeyboardType 改变的组件,并且提供 CustomKeyboardController 来控制自定义键盘的开关。
KeyboardTypeBuilder(
builder: (
BuildContext context,
CustomKeyboardController controller,
) =>
ToggleButton(
builder: (bool active) => Icon(
Icons.sentiment_very_satisfied,
color: active ? Colors.orange : null,
),
activeChanged: (bool active) {
_keyboardPanelType = KeyboardPanelType.emoji;
if (active) {
controller.showCustomKeyboard();
if (!_focusNode.hasFocus) {
SchedulerBinding.instance
.addPostFrameCallback((Duration timeStamp) {
_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 的参数,该参数表示最后显示的系统键盘的高度。此参数可用于为您的自定义键盘设置适当的高度,从而确保无缝且直观的用户体验。
| parameter | description | default |
|---|---|---|
| builder | 一个构建器函数,它根据系统键盘高度返回一个小部件。 | required |
| bodyBuilder | 一个带 readOnly 参数的组件回调 | required |
| resizeToAvoidBottomInset | 跟 Scaffold.resizeToAvoidBottomInset 作用一致 | true |
| controller | 自定义键盘控制器 | null |
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: const Text('ChatDemo(KeyboardBuilder)')),
body: SafeArea(
bottom: true,
child: KeyboardBuilder(
resizeToAvoidBottomInset: true,
builder: (BuildContext context, double? systemKeyboardHeight) {
return Container();
},
bodyBuilder: (bool readOnly) => Column(children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextField(
readOnly: readOnly,
showCursor: true,
onTap: () {
_customKeyboardController.showSystemKeyboard();
},
),
),
KeyboardTypeBuilder(
builder: (
BuildContext context,
CustomKeyboardController controller,
) =>
ToggleButton(
builder: (bool active) => Icon(
Icons.sentiment_very_satisfied,
color: active ? Colors.orange : null,
),
activeChanged: (bool active) {
_keyboardPanelType = KeyboardPanelType.emoji;
if (active) {
controller.showCustomKeyboard();
if (!_focusNode.hasFocus) {
SchedulerBinding.instance
.addPostFrameCallback((Duration timeStamp) {
_focusNode.requestFocus();
});
}
} else {
controller.showSystemKeyboard();
}
},
active: controller.isCustom &&
_keyboardPanelType == KeyboardPanelType.emoji,
),
),
],
),
]),
),
),
);
TextInputScope
KeyboardBinding / KeyboardBindingMixin
你可以直接使用 KeyboardBinding ,或者将 KeyboardBindingMixin 混入到你的 WidgetsFlutterBinding 中。
Future<void> main() async {
KeyboardBinding();
await SystemKeyboard().init();
runApp(const MyApp());
}
KeyboardConfiguration
这个配置包括键盘应该如何构建,它的动画持续时间,它的名字。
| parameter | description | default |
|---|---|---|
| getKeyboardHeight | 返回自定义键盘的高度 | required |
| builder | 包含输入框的主体 | required |
| keyboardName | 自定义键盘的名字 | required |
| showDuration | 自定义键盘打开的时间 | const Duration(milliseconds: 200) |
| hideDuration | 自定义键盘隐藏的时间 | const Duration(milliseconds: 200) |
| resizeToAvoidBottomInset | 跟 Scaffold.resizeToAvoidBottomInset 一样的意思. 如果它不设置,将和 TextInputScope.resizeToAvoidBottomInset 的值相同 | null |
KeyboardConfiguration(
getKeyboardHeight: (double? systemKeyboardHeight) =>
systemKeyboardHeight ?? 346,
builder: () {
return Container();
},
keyboardName: 'custom_number1',
resizeToAvoidBottomInset: true,
),
TextInputScope
如果使用 Scaffold,请确保将 Scaffold.resizeToAvoidBottomInset 设置为 false。
| parameter | description | default |
|---|---|---|
| body | 包含输入框的主体 | required |
| configurations | 自定义键盘配置 | required |
| keyboardHeight | 默认的自定义键盘高度 | 346 |
| resizeToAvoidBottomInset | 跟 Scaffold.resizeToAvoidBottomInset 的意思一样. | true |
late List<KeyboardConfiguration> _configurations;
@override
void initState() {
super.initState();
_configurations = <KeyboardConfiguration>[
KeyboardConfiguration(
getKeyboardHeight: (double? systemKeyboardHeight) =>
systemKeyboardHeight ?? 346,
builder: () {
return Container();
},
keyboardName: 'custom_number',
),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TextInputDemo'),
),
resizeToAvoidBottomInset: false,
body: SafeArea(
bottom: true,
child: TextInputScope(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
children: <Widget>[
TextField(
keyboardType: _configurations[0].keyboardType,
controller: _controller,
decoration: InputDecoration(
hintText:
'The keyboardType is ${_configurations[0].keyboardType.name}',
),
),
],
),
),
configurations: _configurations,
),
),
);
}
扩展方法
TextEditingController 的扩展方法
void insertText(String text)在当前位置插入文本void delete()删除一个字符TextEditingValue deleteText()删除一个字符并且返回删除之后的值,可以根据自己的情况再处理void performAction(TextInputAction action)跟TextInputClient.performAction一样的作用
终章
“天命人,别忘了,
Flutter只是工具,真正的力量依然在你心中。”
“原来,这一切的力量,从未真正离开过我。”天命人低声自语。
爱 Flutter,爱糖果,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果
最最后放上 Flutter Candies 全家桶,真香。