【Flutter】深入剖析TextField组件

前言

在之前学习和使用Flutter过程中肯定会频繁使用到TextField组件。而且在此过程中也频繁出现使用输入框遇到的坑:布局、高度问题、TextField在iOS平台输入中文、## TextField获取/失去焦点以及软键盘获取。

总结这些问题之后就更需要深入了解Flutter平台中TextField的使用和源码剖析。

属性值

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时在初始化后会自动获取焦点并弹起软键盘(一般情况下是会弹起,特殊情况除外)

输入控制

获取输入内容的方式有两种:onChangedcontroller

  • 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方法中实现输入框的展示。在内部多层组件嵌套下(FocusTrapAreaMouseRegion等)核心是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方法中同样是通过多层嵌套(MouseRegionScrollableCompositedTransformaTarget)去看更主要的_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<TextSelectionPoint> 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,
    );
  }
} 

最后

如果你进阶的路上缺乏方向,可以扫描下方二维码加入我们的圈子和安卓开发者们一起学习交流!
以下全部内容都可以在微信中获取!

  • Android进阶学习全套手册

    img

最后,借用我最喜欢的乔布斯语录,作为本文的结尾:

人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值