iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Flutter如何支持放大镜的输入框功能
  • 123
分享到

Flutter如何支持放大镜的输入框功能

2023-06-29 08:06:22 123人浏览 泡泡鱼
摘要

这篇文章将为大家详细讲解有关Flutter如何支持放大镜的输入框功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。功能需求最近需求开发中遇到一个Flutter开发问题,为了优化用户输入体验。产品同学希望能

这篇文章将为大家详细讲解有关Flutter如何支持放大镜的输入框功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

功能需求

最近需求开发中遇到一个Flutter开发问题,为了优化用户输入体验。产品同学希望能够在输入框支持在移动光标过程中可以出现放大镜功能。原先以为是一个小需求,因为原生系统上iOS和安卓印象中是自带这个功能的。在实施开发时才发现原来并不是这样的,Flutter好像并没有去支持原有的功能。

Flutter如何支持放大镜的输入框功能

需求调研

为了确认官方是否支持了输入框放大镜功能,去GitHub项目上搜索issue后发现这个问题在18年就有人提到过,但官方却一直没有去支持实现。

Flutter如何支持放大镜的输入框功能

既然官方没有支持,秉承有轮子我就用的思想继续通过github搜索是否有开发者自定义实现了这个功能。

搜索Magnifier找到了一篇文章是对放大镜的实现,但他并不是在输入框上的实现,只对屏幕手势触摸的地方进行放大。

Flutter如何支持放大镜的输入框功能

因为找不到完全实现输入框放大镜功能,那么只能自行去实现该功能了。可以根据Magnifier来为输入框实现放大镜功能。

需求实现

通过对TextField的使用会发现,当使用光标双击或是长按会出现TextToolBar功能栏,随着光标的移动,上方的编辑栏也会跟着光标进行移动。这个发现正好能够在放大镜功能上运用:跟随光标移动+放大就能够实现最终期望的效果了。

Flutter如何支持放大镜的输入框功能

源码解读

那么在功能实现之前就需要阅读TextField源码了解光标上方的编辑栏是如何实现并且能够跟随光标的。

PS:源码解析使用的是extended_text_field,主因是项目中使用了富文本输入和显示。

ExtendedTextField输入框组件源码找到ExtendedEditableText中视图build方法可以看到CompositedTransfORMTarget_toolbarLayerLink。而这两个已经是实现放大镜功能的关键信息了。

关于CompositedTransformTarget的使用可以在网上搜到很多,作用是来绑定两个View视图。除了CompositedTransformTarget之外还有CompositedTransformFollower。简单理解就是CompositedTransformFollower是绑定者,CompositedTransformTarget是被绑定者,前者跟随后者。_toolbarLayerLink就是跟随光标操作栏的绑定媒介。

return CompositedTransformTarget(  link: _toolbarLayerLink, // 操作工具  child: Semantics(    ...    child: _Editable(      key: _editableKey,      startHandleLayerLink: _startHandleLayerLink, //左边光标位置      endHandleLayerLink: _endHandleLayerLink, //右边光标位置      textSpan: _buildTextSpan(context),      value: _value,      cursorColor: _cursorColor,      ......    ),  ),);

通过源码查询找到_toolbarLayerLink另一个使用者ExtendedTextSelectionOverlay

void createSelectionOverlay({ //创建操作栏  ExtendedRenderEditable? renderObject,  bool showHandles = true,}) {  _selectionOverlay = ExtendedTextSelectionOverlay(     clipboardStatus: _clipboardStatus,    context: context,    value: _value,    debugRequiredFor: widget,    toolbarLayerLink: _toolbarLayerLink,    startHandleLayerLink: _startHandleLayerLink,    endHandleLayerLink: _endHandleLayerLink,    renderObject: renderObject ?? renderEditable,    selectionControls: widget.selectionControls,   .....  );    ...

通过源码查询可以找到CompositedTransformFollower组件使用,可以通过代码看到selectionControls!.buildToolbar就是编辑栏的实现。

return Directionality(  textDirection: Directionality.of(this.context),  child: FadeTransition(    opacity: _toolbarOpacity,    child: CompositedTransformFollower( // 操作栏的跟踪组件      link: toolbarLayerLink,      showWhenUnlinked: false,      offset: -editingRegion.topLeft,      child: Builder(        builder: (BuildContext context) {          return selectionControls!.buildToolbar(             context,            editingRegion,            renderObject.preferredLineHeight,            midpoint,            endpoints,            selectionDelegate!,            clipboardStatus!,            renderObject.lastSecondaryTapDownPosition,          );        },      ),    ),  ),);

然后返回去找selectionControls是如何实现的。在_ExtendedTextFieldStatebuild方法中可以找到textSelectionControls默认创建。由于安卓和iOS平台存在差异性,因此有cupertinoTextSelectionControlsmaterialTextSelectionControls两个selectionControls。

switch (theme.platform) {  case TargetPlatform.iOS:    final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);    forcePressEnabled = true;    textSelectionControls ??= cupertinoTextSelectionControls;    ......    break;     ......  case TargetPlatform.Android:  case TargetPlatform.fuchsia:    forcePressEnabled = false;    textSelectionControls ??= materialTextSelectionControls;   .....    break;    ....}

这里就只看MaterialTextSelectionControls源码实现。布局实现在_TextSelectionControlsToolbar中。_TextSelectionHandlePainter是绘制光标样式的方法。

 @override  Widget build(BuildContext context) {      // 左右光标的定位位置    final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0];    // 这里做了判断是否是两个光标    final TextSelectionPoint endTextSelectionPoint = widget.endpoints.length > 1      ? widget.endpoints[1]      : widget.endpoints[0];    final Offset anchorAbove = Offset(      widget.globalEditableRegion.left + widget.selectionMidpoint.dx,      widget.globalEditableRegion.top + startTextSelectionPoint.point.dy - widget.textLineHeight - _kToolbarContentDistance,    );    final Offset anchorBelow = Offset(      widget.globalEditableRegion.left + widget.selectionMidpoint.dx,      widget.globalEditableRegion.top + endTextSelectionPoint.point.dy + _kToolbarContentDistanceBelow,    );   ....    return TextSelectionToolbar(      anchorAbove: anchorAbove, // 左边光标      anchorBelow: anchorBelow,// 右边光标      children: itemDatas.asMap().entries.map((MapEntry<int, _TextSelectionToolbarItemData> entry) {        return TextSelectionToolbarTextButton(          padding: TextSelectionToolbarTextButton.getPadding(entry.key, itemDatas.length),          onPressed: entry.value.onPressed,          child: Text(entry.value.label),         );      }).toList(), // 每个编辑操作的按钮功能    );  }}/// 安卓选中样式绘制(默认是圆点加上一个箭头)class _TextSelectionHandlePainter extends CustomPainter {  _TextSelectionHandlePainter({ required this.color });  final Color color;  @override  void paint(canvas canvas, Size size) {    final Paint paint = Paint()..color = color;    final double radius = size.width/2.0;    final Rect circle = Rect.fromCircle(center: Offset(radius, radius), radius: radius);    final Rect point = Rect.fromLTWH(0.0, 0.0, radius, radius);    final Path path = Path()..addOval(circle)..addRect(point);    canvas.drawPath(path, paint);  }  @override  bool shouldRepaint(_TextSelectionHandlePainter oldPainter) {    return color != oldPainter.color;  }}

功能复刻

了解源码功能之后就能拷贝MaterialTextSelectionControls实现来完成放大镜功能了。同样是继承TextSelectionControls,实现MaterialMagnifierControls功能。

主要修改点在_MagnifierControlsToolbar的实现以及MaterialMagnifier功能

MagnifierControlsToolbar

其中的build方法返回了widget.endpoints光标的定位信息,定位信息去计算出偏移量。最后将两个光标信息入参到MaterialMagnifier组件。

const double _kHandleSize = 22.0;const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;const double _kToolbarContentDistance = 8.0;class MaterialMagnifierControls extends TextSelectionControls {  @override  Size getHandleSize(double textLineHeight) =>      const Size(_kHandleSize, _kHandleSize);  @override  Widget buildToolbar(    BuildContext context,    Rect globalEditableRegion,    double textLineHeight,    Offset selectionMidpoint,    List<TextSelectionPoint> endpoints,    TextSelectionDelegate delegate,    ClipboardStatusNotifier clipboardStatus,    Offset? lastSecondaryTapDownPosition,  ) {    return _MagnifierControlsToolbar(      globalEditableRegion: globalEditableRegion,      textLineHeight: textLineHeight,      selectionMidpoint: selectionMidpoint,      endpoints: endpoints,      delegate: delegate,      clipboardStatus: clipboardStatus,    );  }  @override  Widget buildHandle(      BuildContext context, TextSelectionHandleType type, double textHeight,      [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) {    return const SizedBox();  }  @override  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight,      [double? startGlyphHeight, double? endGlyphHeight]) {    switch (type) {      case TextSelectionHandleType.left:        return const Offset(_kHandleSize, 0);      case TextSelectionHandleType.right:        return Offset.zero;      default:        return const Offset(_kHandleSize / 2, -4);    }  }}class _MagnifierControlsToolbar extends StatefulWidget {  const _MagnifierControlsToolbar({    Key? key,    required this.clipboardStatus,    required this.delegate,    required this.endpoints,    required this.globalEditableRegion,    required this.selectionMidpoint,    required this.textLineHeight,  }) : super(key: key);  final ClipboardStatusNotifier clipboardStatus;  final TextSelectionDelegate delegate;  final List<TextSelectionPoint> endpoints;  final Rect globalEditableRegion;  final Offset selectionMidpoint;  final double textLineHeight;  @override  _MagnifierControlsToolbarState createState() =>      _MagnifierControlsToolbarState();}class _MagnifierControlsToolbarState extends State<_MagnifierControlsToolbar>    with TickerProviderStateMixin {  Offset offset1 = Offset.zero;  Offset offset2 = Offset.zero;  void _onChangedClipboardStatus() {    setState(() {    });  }  @override  void initState() {    super.initState();    widget.clipboardStatus.addListener(_onChangedClipboardStatus);    widget.clipboardStatus.update();  }  @override  void didUpdateWidget(_MagnifierControlsToolbar oldWidget) {    super.didUpdateWidget(oldWidget);    if (widget.clipboardStatus != oldWidget.clipboardStatus) {      widget.clipboardStatus.addListener(_onChangedClipboardStatus);      oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);    }    widget.clipboardStatus.update();  }  @override  void dispose() {    super.dispose();    if (!widget.clipboardStatus.disposed) {      widget.clipboardStatus.removeListener(_onChangedClipboardStatus);    }  }  @override  Widget build(BuildContext context) {    TextSelectionPoint point = widget.endpoints[0];    if(widget.endpoints.length > 1){      if(offset1 != widget.endpoints[0].point){        point =  widget.endpoints[0];        offset1 = point.point;      }      if(offset2 != widget.endpoints[1].point){        point =  widget.endpoints[1];        offset2 = point.point;      }    }    final TextSelectionPoint startTextSelectionPoint = point;    final Offset anchorAbove = Offset(      widget.globalEditableRegion.left + startTextSelectionPoint.point.dx,      widget.globalEditableRegion.top +          startTextSelectionPoint.point.dy -          widget.textLineHeight -          _kToolbarContentDistance,    );    final Offset anchorBelow = Offset(      widget.globalEditableRegion.left + startTextSelectionPoint.point.dx,      widget.globalEditableRegion.top +          startTextSelectionPoint.point.dy +          _kToolbarContentDistanceBelow,    );    return  MaterialMagnifier(        anchorAbove: anchorAbove,        anchorBelow: anchorBelow,        textLineHeight: widget.textLineHeight,    );  }}final TextSelectionControls materialMagnifierControls =    MaterialMagnifierControls();

MaterialMagnifier

MaterialMagnifier是参考Widget Magnifier放大镜的实现。这里是引入了安卓的一些布局参数来实现,iOS是另外定制了布局参数可以参考Flutter官方源码定制iOS布局。

放大镜实现方法主要是BackdropFilterImageFilter来实现的,根据Matrix4scaletranslate操作完成放大功能。

const double _kToolbarScreenPadding = 8.0;const double _kToolbarHeight = 44.0;class MaterialMagnifier extends StatelessWidget {  const MaterialMagnifier({    Key? key,    required this.anchorAbove,    required this.anchorBelow,    required this.textLineHeight,    this.size = const Size(90, 50),    this.scale = 1.7,  }) : super(key: key);  final Offset anchorAbove;  final Offset anchorBelow;  final Size size;  final double scale;  final double textLineHeight;  @override  Widget build(BuildContext context) {    final double paddingAbove =        MediaQuery.of(context).padding.top + _kToolbarScreenPadding;    final double availableHeight = anchorAbove.dy - paddingAbove;    final bool fitsAbove = _kToolbarHeight <= availableHeight;    final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);    final Matrix4 updatedMatrix = Matrix4.identity()      ..scale(1.1,1.1)      ..translate(0.0,-50.0);    Matrix4 _matrix = updatedMatrix;    return Container(      child: Padding(        padding: EdgeInsets.fromLTRB(          _kToolbarScreenPadding,          paddingAbove,          _kToolbarScreenPadding,          _kToolbarScreenPadding,        ),        child: Stack(          children: <Widget>[            CustomSingleChildLayout(              delegate: TextSelectionToolbarLayoutDelegate(                anchorAbove: anchorAbove - localAdjustment,                anchorBelow: anchorBelow - localAdjustment,                fitsAbove: fitsAbove,              ),              child: ClipRRect(                borderRadius: BorderRadius.circular(10),                child: BackdropFilter(                  filter: ImageFilter.matrix(_matrix.storage),                  child: CustomPaint(                    painter: const MagnifierPainter(color: Color(0xFFdfdfdf)),                    size: size,                  ),                ),              ),            ),          ],        ),      ),    );  }}

交互优化

实现放大镜功能之外还需要控制显示,由于在拖动状态下才显示放大镜,隐藏操作栏功能,因此需要去监听手势状态信息。

手势监听是在_TextSelectionHandleOverlayState中,需要去监听onPanStartonPanUpdateonPanEndonPanCancel这几个状态。

状态行动
onPanStart隐藏操作栏、显示放大镜
onPanUpdate显示放大镜,获取到偏移信息
onPanEnd显示操作栏、隐藏放大镜
onPanCancel显示操作栏、隐藏放大镜
final Widget child = GestureDetector(  behavior: HitTestBehavior.translucent,  dragStartBehavior: widget.dragStartBehavior,  onPanStart: _handleDragStart,  onPanUpdate: _handleDragUpdate,  onPanEnd: _handleDragEnd,  onPanCancel: _handleDraGCancel,  onTap: _handleTap,  child: Padding(    padding: EdgeInsets.only(      left: padding.left,      top: padding.top,      right: padding.right,      bottom: padding.bottom,    ),    child: widget.selectionControls!.buildHandle(      context,      type,      widget.renderObject.preferredLineHeight,          () {},    ),  ),);

在开始拓展手势时展示放大镜,隐藏操作。_builderMagnifier嵌套在OverlayEntry组件在Overlay上插入,实现方式是和操作栏完全一样的。

void _handleDragStart(DragStartDetails details) {  final Size handleSize = widget.selectionControls!.getHandleSize(    widget.renderObject.preferredLineHeight,  );  _dragPosition = details.globalPosition + Offset(0.0, -handleSize.height);  widget.showMagnifierBarFunc(); // 回调展示放大镜功能  toolBarRecover = widget.hideToolbarFunc();}void showMagnifierBar() {  assert(_magnifier == null);  _magnifier = OverlayEntry(builder: _builderMagnifier);  Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!      .insert(_magnifier!);}

同理在拖拽结束时去隐藏放大镜,重新创建操作栏恢复显示。

void _handleDragEnd(DragEndDetails details) {  widget.hideMagnifierBarFunc();  if (toolBarRecover) {    widget.showToolbarFunc();    toolBarRecover = false;  }}void hideMagnifierBar() {  if (_magnifier != null) {    _magnifier!.remove();    _magnifier = null;  }}

最终效果

最后实现效果如下,通过移动光标可显示放大镜功能,松开手势就是操作栏显示恢复。

Flutter如何支持放大镜的输入框功能

关于“Flutter如何支持放大镜的输入框功能”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: Flutter如何支持放大镜的输入框功能

本文链接: https://www.lsjlt.com/news/323611.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Flutter如何支持放大镜的输入框功能
    这篇文章将为大家详细讲解有关Flutter如何支持放大镜的输入框功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。功能需求最近需求开发中遇到一个Flutter开发问题,为了优化用户输入体验。产品同学希望能...
    99+
    2023-06-29
  • Flutter开发之支持放大镜的输入框功能实现
    目录功能需求需求调研需求实现源码解读功能复刻最终效果功能需求 最近需求开发中遇到一个Flutter开发问题,为了优化用户输入体验。产品同学希望能够在输入框支持在移动光标过程中可以出现...
    99+
    2024-04-02
  • flutter微信聊天输入框功能如何实现
    这篇文章主要讲解了“flutter微信聊天输入框功能如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“flutter微信聊天输入框功能如何实现”吧!高仿微信聊天输入框,效果图如下(目前都...
    99+
    2023-07-05
  • html5如何实现放大镜功能
    今天小编给大家分享一下html5如何实现放大镜功能的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。html5实现放大镜功能的方...
    99+
    2023-07-05
  • js如何自制图片放大镜功能
    这篇文章给大家分享的是有关js如何自制图片放大镜功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体内容如下注释:small img size:600x400big ...
    99+
    2024-04-02
  • HTML5如何给输入框添加语音输入功能
    这篇文章给大家分享的是有关HTML5如何给输入框添加语音输入功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。大家可以看到在输入框右边的麦克风图标,点击麦克风就能够进行语音识别了...
    99+
    2024-04-02
  • JS中如何实现Select下拉框支持输入模糊查询
    这篇文章主要为大家展示了“JS中如何实现Select下拉框支持输入模糊查询”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JS中如何实现Select下拉框支持输入...
    99+
    2024-04-02
  • 基于Vue如何实现商品主图放大镜功能
    这篇文章主要介绍基于Vue如何实现商品主图放大镜功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!实现原理放大镜的原理用一句话概括,就是根据小图上的鼠标位置去定位大图。图1 原理图(...
    99+
    2024-04-02
  • javascript如何实现input输入框模糊提示功能
    这篇文章主要为大家展示了“javascript如何实现input输入框模糊提示功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“javascript如何实现in...
    99+
    2024-04-02
  • jQuery如何实现IE输入框完成placeholder标签功能
    这篇文章主要介绍了jQuery如何实现IE输入框完成placeholder标签功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。如果在输入框...
    99+
    2024-04-02
  • 如何使用JavaScript实现select所支持的功能
    小编给大家分享一下如何使用JavaScript实现select所支持的功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧...
    99+
    2024-04-02
  • jquery如何实现输入框数字的增加和减少功能
    本篇内容主要讲解“jquery如何实现输入框数字的增加和减少功能”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“jquery如何实现输入框数字的增加和减少功能”吧!代码实现:首先,需要引入jque...
    99+
    2023-07-05
  • bootstrap如何通过加减按钮实现输入框组功能
    这篇文章将为大家详细讲解有关bootstrap如何通过加减按钮实现输入框组功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。实现效果图如下:当我点击 + 按钮时,会添加一...
    99+
    2024-04-02
  • 如何用CSS实现简单大气的输入框
    这篇“如何用CSS实现简单大气的输入框”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“如何用CSS实现简单大气的输入框”文章吧...
    99+
    2023-07-05
  • Angular如何使用输入框实现自定义验证功能
    这篇文章将为大家详细讲解有关Angular如何使用输入框实现自定义验证功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。此插件使用angular.js、JQuery实现。...
    99+
    2024-04-02
  • C++ 如何支持移动应用程序的离线功能
    非常抱歉,由于您没有提供文章标题,我无法为您生成一篇高质量的文章。请您提供文章标题,我将尽快为您生成一篇优质的文章。...
    99+
    2024-05-16
  • windows输入法的联想功能如何关闭
    这篇文章主要介绍了windows输入法的联想功能如何关闭的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇windows输入法的联想功能如何关闭文章都会有所收获,下面我们一起来看看吧。关闭输入法的联想功能:由于不同...
    99+
    2023-07-02
  • windows输入法的记忆功能如何关闭
    这篇文章主要介绍了windows输入法的记忆功能如何关闭的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇windows输入法的记忆功能如何关闭文章都会有所收获,下面我们一起来看看吧。关闭输入法记忆的方法:首先按下...
    99+
    2023-07-02
  • 如何在MongoDB中实现数据的多语言支持功能
    如何在MongoDB中实现数据的多语言支持功能摘要:随着全球化的发展,越来越多的应用程序需要支持多语言功能。本文将介绍如何在MongoDB中实现数据的多语言支持功能,包括数据结构设计、数据存储和数据查询等方面。同时,为了更好地理解和实操本文...
    99+
    2023-10-22
    MongoDB 多语言支持
  • vue2.0如何实现移动端的输入框实时检索更新列表功能
    这篇文章将为大家详细讲解有关vue2.0如何实现移动端的输入框实时检索更新列表功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。效果图html  &l...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作