属性值
TextField组件本身具备多种属性,支持很多参数设置来实现不同样式效果。 TextField组件可直接上手使用,但默认样式和输入规则并不一定是需求开发中想要的(实话说默认样式并不好看)。下面就通过TextField组件属性介绍来自定义属于自己的输入框吧。
TextField();
基础功能
obscureText: true表示隐藏输入内容,类似密码输入
readOnly: true表示输入框禁止输入
textCapitalization:控制输入内容大小写(words 首字母大写、sentences 句子首字母大写、characters 所有字母大写),可能存在兼容性问题会不生效。
minLines & maxLines: 最小行数和最大行数设置,若只设置最大行数输入框高度就是maxLines的高度,否则是minLines的高度。
maxLength: 输入字符限制器。在输入框下方会出现输入字符数量(一般也不希望透出计数)
样式
样式设置主要未然光标和输入框装饰内容设置。
光标
光标支持自定义,支持设置颜色、高度、宽度、圆角以及是否展示光标。
cursorColor: Colors.black,
cursorHeight: 10,
cursorWidth: 5,
cursorRadius: Radius.circular(10),
showCursor: false,
装饰
前面提到输入框默认样式比较普通,日常开发中需要设置输入框样式。自定义输入框样式是可以通过decoration来实现的。
外框
外框可以使用内置官方已有样式,也能通过自定义边框来实现。
decoration: InputDecoration(
border: OutlineInputBorder()
)
hint提示
默认未输入内容的提示信息
decoration: InputDecoration(
hintText: "请输入"
)
悬浮提示
悬浮提示就是当获取焦点后的输入信息的提示(不太常用)
decoration: InputDecoration(
lableText: "请输入"
)
另外InputDecoration还支持设置其他border,例如错误状态、获取交点状态、不可用状态等都能自定义。
软键盘
textInputAction: 软键盘按钮样式和功能设置(none、unspecified、done、go、search、next等)实现软键盘按钮不同操作能力。
focusNode: 控制焦点使用,通过它实现弹起或收起软键盘能力。
autofocus: 是否主动获取焦点,true时在初始化后会自动获取焦点并弹起软键盘(一般情况下是会弹起,特殊情况除外)
输入控制
获取输入内容的方式有两种:onChanged和controller。
onChanged
TextField(
onChanged:(text){
// 这个可以返回输入文本
}
)
controller
TextEditingController controller = TextEditingController();
controller.addListener((){
// 这个可以返回输入文本
controller.text;
});
TextField(
controller: controller,
)
inputFormatters: 输入字符限制,通过自定义限制输入内容。
这里举例内置的LengthLimitingTextInputFormatter用来限制输入字符数。这和基础功能中maxLength是一样的作用,不同点在于maxLength会携带计数器。
源码解析
TextField继承StatefulWidget,所以就直接来看_TextFieldState
_TextFieldState
_TextFieldState中的build方法中实现输入框的展示。在内部多层组件嵌套下(FocusTrapArea、MouseRegion等)核心是EditableText组件。 另外在build方法中还有switch (theme.platform)方法,由于各个平台中对于输入框的实现存在差异,例如安卓和iOS输入框的光标以及选择状态样式存在差异。因此这里的switch (theme.platform)主要就是对于平台差异性做一些设置,代码可见主要是光标和textSelectionControls文本选择上有所不同。
switch (theme.platform) {
case TargetPlatform.iOS:
final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
forcePressEnabled = true;
textSelectionControls ??= cupertinoTextSelectionControls;
paintCursorAboveText = true;
cursorOpacityAnimates = true;
cursorColor ??= selectionTheme.cursorColor ?? cupertinoTheme.primaryColor;
selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
cursorRadius ??= const Radius.circular(2.0);
cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
autocorrectionTextRectColor = selectionColor;
break;
......
case TargetPlatform.android:
case TargetPlatform.fuchsia:
forcePressEnabled = false;
textSelectionControls ??= materialTextSelectionControls;
paintCursorAboveText = false;
cursorOpacityAnimates = false;
cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
break;
EditableTextState
_TextFieldState中平台差异性判断之后,接下来就是EditableText组件内部。同样EditableText是继承自StatefulWidget,略过之后直接看EditableTextState。在build方法中同样是通过多层嵌套(MouseRegion、Scrollable、CompositedTransformaTarget)去看更主要的_Editable。
_Editable
_Editable是继承自MultiChildRenderObjectWidget,绝大多数布局控件都是继承MultiChildRenderObjectWidget实现的。_Editable实现的渲染对象是RenderEditable
RenderEditable
RenderEditable就是RenderBox,就到了底层布局的测量、绘制、更新的过程。源码分析就直接看paint方法实现。
_paintContents部分是绘制文本内容,内部由TextPainter对文本绘制的实现。
@override
void paint(PaintingContext context, Offset offset) {
_computeTextMetricsIfNeeded();
if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintContents,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer.layer = null;
_paintContents(context, offset);
}
_paintHandleLayers(context, getEndpointsForSelection(selection!));
}
_paintHandleLayers是文本选择器的绘制,若endpoints == 2则是段落选择。否则只是单选就只有一个光标选择。
void _paintHandleLayers(PaintingContext context, List endpoints) {
Offset startPoint = endpoints[0].point;
startPoint = Offset(
startPoint.dx.clamp(0.0, size.width),
startPoint.dy.clamp(0.0, size.height),
);
context.pushLayer(
LeaderLayer(link: startHandleLayerLink, offset: startPoint),
super.paint,
Offset.zero,
);
if (endpoints.length == 2) {
Offset endPoint = endpoints[1].point;
endPoint = Offset(
endPoint.dx.clamp(0.0, size.width),
endPoint.dy.clamp(0.0, size.height),
);
context.pushLayer(
LeaderLayer(link: endHandleLayerLink, offset: endPoint),
super.paint,
Offset.zero,
);
}
}