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,例如键盘或系统菜单,
/// 从而完全遮挡内容。
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.bottom -
MediaQuery.of(context).viewPadding.bottom -
MediaQuery.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')
了. 相关问题 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
用于管理系统键盘的高度并提供处理键盘布局更改的功能。
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemKeyboard().init();
runApp(const MyApp());
}
KeyboardBuilder
如果我们想要关闭系统键盘,并且保持输入框的不丢失焦点,我们没法再使用
SystemChannels.textInput.invokeMethod<void>('TextInput.hide')
了. 相关问题 https://github.com/flutter/flutter/issues/16863
下面的代码是一种变通方案,
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,
),
),
],
),
]),
),
),
);
Full Demo
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,
),
),
);
}
Full Demo
扩展方法
TextEditingController 的扩展方法
-
void insertText(String text)在当前位置插入文本 -
void delete()删除一个字符 -
TextEditingValue deleteText()删除一个字符并且返回删除之后的值,可以根据自己的情况再处理 -
void performAction(TextInputAction action)跟TextInputClient.performAction一样的作用
终章
❝
“天命人,别忘了,
Flutter
只是工具,真正的力量依然在你心中。”
❞
❝“原来,这一切的力量,从未真正离开过我。”天命人低声自语。
❞
Flutter
,爱
糖果
,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果。