Flutter TextFiled的哪些坑
在Flutter中输入框是重度交互使用场景,但其中很多坑爹体验问题一直官方都没给出好的解决方案,本文将介绍一些Flutter输入框的坑爹问题;
1. Android未输入字符时光标高度小于输入字符时高度
具体表现行为如下:
此小细节问题在稀土掘金/B站漫画/青藤之恋等使用Flutter的APP都存在,主要原因使用官方在使用定义iOS/Android两个平台的光标有不同实现机制;核心实现类在Editable.dart中
void _computeCaretPrototype() {
assert(defaultTargetPlatform != null);
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
_caretPrototype = Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight + 2);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
_caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, cursorHeight - 2.0 * _kCaretHeightOffset);
break;
}
}
iOS平台
实验测试iOS为输入字符和输入字符高度变化如下:
因此光标区域高度
未输入字符时:cursorHeight = 21.0 - 0.0 = 21.0 ;
输入字符时:cursorHeight = 20.0 - (-1.0) = 21.0 ;
高度未发生变化,可以发现其实iOS也存在一个像素偏差,只是肉眼不易发现而言;
Android平台
在Android平台下测试未输入字符高度和输入字符高度变化如下:
未输入字符时:cursorHeight = 17.0 - 2.0 = 15.0 ;
输入字符时: cursorHeight = 19.0 - 0.3 = 18.7 ;
因此可以明显看出Android平台下输入字符后光标突然变大了一些;
修复方案
可以参考之前提过的PR:github.com/flutter/flu…
核心修复就是将Android平台计算光标高度和iOS保持一致,FIX-ADD 代码如下:
void paintRegularCursor(Canvas canvas, RenderEditable renderEditable, Color caretColor, TextPosition textPosition) {
...
caretRect = caretRect.shift(renderEditable._paintOffset);
///FIX-ADD
caretRect = Rect.fromLTRB(caretRect.left, 0.0, caretRect.right, renderEditable.cursorHeight + 2.0);
final Rect integralRect = caretRect.shift(renderEditable._snapToPhysicalPixel(caretRect.topLeft));
...
}
2. 输入框自动读取粘贴板
这个问题在一些Flutter2.x版本存在(在高版本3.0后应该已经修复),特别是在小米手机上因为照明弹很容易引起监管问题突出,核心也是这个TextFiled的基类text_selection.dart中:
/// Check the [Clipboard] and update [value] if needed.
Future<void> update() async {
switch (defaultTargetPlatform) {
// Android 12 introduces a toast message that appears when an app reads
// the content on the clipboard. To avoid unintended access, both the
// Flutter engine and the Flutter framework need to be updated to use the
// appropriate API to check whether the clipboard is empty.
// As a short-term workaround, always show the paste button.
// TODO(justinmc): Expose `hasStrings` in `Clipboard` and use that instead
// https://github.com/flutter/flutter/issues/74139
case TargetPlatform.android:
// Intentionally fall through.
// iOS 14 added a notification that appears when an app accesses the
// clipboard. To avoid the notification, don't access the clipboard on iOS,
// and instead always show the paste button, even when the clipboard is
// empty.
// TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
// won't trigger the notification.
// https://github.com/flutter/flutter/issues/60145
case TargetPlatform.iOS:
value = ClipboardStatus.pasteable;
return;
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
break;
}
ClipboardData? data;
try {
data = await Clipboard.getData(Clipboard.kTextPlain);
} catch (stacktrace) {
// In the case of an error from the Clipboard API, set the value to
// unknown so that it will try to update again later.
if (_disposed || value == ClipboardStatus.unknown) {
return;
}
value = ClipboardStatus.unknown;
return;
}
...
}
这里的在Android/iOS平台下提前return不走下面读取系统粘贴板方法了;
3. TextFiled的最小高度设置
此问题怎么说呢,是个问题也不算什么大问题,主要场景在类似微信输入框自生长高度时候场景会出现
在实践这个微信键盘输入生长案例时候会发现TextFiled无法设置最小高度,这个主要由于TextFiled内部计算最小高度大概是48,因此当输入一行的最小高度<48时候,TextFiled是无法满足需求的,此时你可以换CupertinoTextField实现类似效果,不单独展诉此问题~
4. 监听系统键盘弹出收缩状态
目前Flutter在这个API没有提供,一些第三方插件实现思路大多数监听viewInsets.bottom,
具体核心代码如下所示:
MediaQuery.of(context).viewInsets.bottom > 0 ? '键盘弹出' : '键盘收起'
正常场景下此方法没有任何问题,但是就怕骚操作场景会出现一些异常现象,如果各位有更好的方法麻烦告知下我,谢谢~
5. 焦点控制和输入框顶起
焦点控制类是FocusNode使用,核心代码如下:
///获取焦点
FocusScope.of(context).requestFocus(focusNode);
///释放焦点
focusNode?.unfocus();
输入框因键盘顶起核心设置 resizeToAvoidBottomPadding
总括
从使用来看,确实坑有点多,目前来看Flutter体验小细节还需要持续打磨打磨,其他它逐步完善吧~