老铁记得 转发 ,猫哥会出现更多 Flutter 好文~~~~

微信 flutter 研修群 ducafecat

原文

https://rlesovyi.medium.com/w...

代码

https://github.com/MatrixDev/...

注释

申明式用户界面在 Flutter 是相当不错,易于应用,它是十分迷人的应用尽可能。然而很多时候,开发人员只是做得太过火了ーー用申明的形式编写所有货色,即便有时候工作能够以更强制性的形式更无效、更容易了解。

每个人都应该明确的---- 在陈说和命令式编程之间必须有一个均衡。每种办法都有本人的用处,每种办法在某些工作上都比其余办法更加杰出。

在本系列文章中,我将形容如何通过从头创立自定义小部件来解决不同的问题。每一个都比前一个略微简单一点。

思考

在查看代码之前,咱们须要晓得一些根本的事件。

Widget ー只是一个不可变的(最好是 const)类,它蕴含 Elements 和 RenderObjects 的配置属性。它还负责创立上述元素和渲染对象。须要了解的重要事件ー小部件从不蕴含状态或任何业务逻辑,只是传递它们。

元素ー是负责理论 UI 树的实体。它蕴含对所有子元素的援用,以及(不像 Widget)对其父元素的援用。元素在大多数状况下都会被重用,除非键或小部件被更改。因而,如果 onlyWidget 属性被更改,即便调配了新的 Widget,Element 也将放弃不变。

State ー只不过是 Element 外部的一个用户定义类,它还公开了一些来自它的回调。

RenderObject ーー负责理论尺寸的计算、子元素的搁置、绘制、触摸事件的解决等。这些对象与 Android 或其余框架的经典视图十分类似。

为什么咱们同时领有元素和渲染对象?因为效率高。每个小部件都有各自的元素,但只有一些有渲染对象。因为这一点,很多布局,触摸和其余档次遍历调用能够省略。

代码

第一个例子是一个非常简单的小部件,它在文本不适宜时用省略号缩放文本。为什么咱们须要这样一个小部件时,内置的文本曾经省略号反对你可能会问?答案很简略---- 到目前为止,它只是通过文字而不是字符来表白 https://github.com/flutter/fl...

那么,咱们开始吧。Flutter 有许多内置的基类和 mixin,它们将帮忙构建齐全自定义的小部件。以下是其中的一些:

  • LeafRenderObjectWidget 没有 child
  • SingleChildRenderObjectWidget 一个 child
  • MultiChildRenderObjectWidget 多个 child

在咱们的例子中,咱们将应用 LeafRenderObjectWidget,因为咱们只须要渲染文本,并且不会有子节点:

enum Ellipsis { start, middle, end }class EllipsizedText extends LeafRenderObjectWidget {  final String text;  final TextStyle? style;  final Ellipsis ellipsis;  const EllipsizedText(    this.text, {    Key? key,    this.style,    this.ellipsis = Ellipsis.end,  }) : super(key: key);  @override  RenderObject createRenderObject(BuildContext context) {    return RenderEllipsizedText()..widget = this;  }  @override  void updateRenderObject(BuildContext context, RenderEllipsizedText renderObject) {    renderObject.widget = this;  }}

咱们创立了咱们的 Widget,惟一不同寻常的是有两种办法:

  • createRenderObject — 负责理论创立咱们的 RenderObject
  • updateRenderObject — 当 Widget 的数据发生变化但 RenderObject 放弃不变时,将调用 updateRenderObject ー。在这种状况下,咱们须要更新 RenderObject 中的数据,否则它将出现旧文本

我还须要留神,将每个值从小部件复制到 RenderObject 是首选的。然而我会通过整个 Widget,因为不管怎样它们都是不可变的(而且我懒得编写所有的样板代码)。

当初让咱们从理论的渲染对象开始:

class RenderEllipsizedText extends RenderBox {  var _widgetChanged = false;  var _widget = const EllipsizedText('');  set widget(EllipsizedText widget) {    if (_widget.text == widget.text &&        _widget.style == widget.style &&        _widget.ellipsis == widget.ellipsis) {      return;    }    _widgetChanged = true;    _widget = widget;    markNeedsLayout();  }}

在这里,咱们定义了所有的变量,并编写了一个 setter 来理论更新它们。还有一个查看值是否理论产生了更改的防护措施ー如果没有更改,则没有必要从新计算省略号和重绘文本。

当初咱们须要布局渲染对象。

class RenderEllipsizedText extends RenderBox {  // ...  var _constraints = const BoxConstraints();  @override  void performLayout() {    if (!_widgetChanged && _constraints == constraints && hasSize) {      return;    }    _widgetChanged = false;    _constraints = constraints;    size =_ellipsize(      minWidth: constraints.minWidth,      maxWidth: constraints.maxWidth,    );  }}

布局的过程相当简略。所有咱们须要做的ー依据提供给咱们的束缚计算渲染对象的大小。束缚只形容咱们必须恪守的最小和最大规模。另外,如果没有任何变动,并且在以前的布局传递过程中曾经计算了大小,则增加额定的查看。

理论创立省略号文本的过程相当繁琐,而且必定有更好的解决方案,但我抉择应用二进制搜寻来寻找最佳匹配。

class RenderEllipsizedText extends RenderBox {  // ...  final _textPainter = TextPainter(textDirection: TextDirection.ltr);  Size _ellipsize({required double minWidth, required double maxWidth}) {    final text = _widget.text;    if (_layoutText(length: text.length, minWidth: minWidth) > maxWidth) {      var left = 0;      var right = text.length - 1;      while (left < right) {        final index = (left + right) ~/ 2;        if (_layoutText(length: index, minWidth: minWidth) > maxWidth) {          right = index;        } else {          left = index + 1;        }      }      _layoutText(length: right - 1, minWidth: minWidth);    }    return constraints.constrain(Size(_textPainter.width, _textPainter.height));  }}

我不会讲完所有这些逻辑(如果你违心,你能够通过它来浏览)。然而重要的是 TextPainter 是用来计算文本大小的。如果文本大小长于咱们的束缚,我会尽量使它越来越短,直到它合乎咱们的束缚。

_layoutText 用来计算咱们裁剪后的文本大小:

double _layoutText({required int length, required double minWidth}) {  final text = _widget.text;  final style = _widget.style;  final ellipsis = _widget.ellipsis;  String ellipsizedText = '';  switch (ellipsis) {    case Ellipsis.start:      if (length > 0) {        ellipsizedText = text.substring(text.length - length, text.length);        if (length != text.length) {          ellipsizedText = '...' + ellipsizedText;        }      }      break;    case Ellipsis.middle:      if (length > 0) {        ellipsizedText = text;        if (length != text.length) {          var start = text.substring(0, (length / 2).round());          var end = text.substring(text.length - start.length, text.length);          ellipsizedText = start + '...' + end;        }      }      break;    case Ellipsis.end:      if (length > 0) {        ellipsizedText = text.substring(0, length);        if (length != text.length) {          ellipsizedText = ellipsizedText + '...';        }      }      break;  }  _textPainter.text = TextSpan(text: ellipsizedText, style: style);  _textPainter.layout(minWidth: minWidth, maxWidth: double.infinity);  return _textPainter.width;}

差不多就是这样了,咱们剩下要做的就是——实际上画出咱们的文本。

@overridevoid paint(PaintingContext context, Offset offset) {  _textPainter.paint(context.canvas, offset);}

© 猫哥

https://ducafecat.tech/

https://github.com/ducafecat

往期

开源

GetX Quick Start

https://github.com/ducafecat/...

新闻客户端

https://github.com/ducafecat/...

strapi 手册译文

https://getstrapi.cn

微信探讨群 ducafecat

系列汇合

译文

https://ducafecat.tech/catego...

开源我的项目

https://ducafecat.tech/catego...

Dart 编程语言根底

https://space.bilibili.com/40...

Flutter 零根底入门

https://space.bilibili.com/40...

Flutter 实战从零开始 新闻客户端

https://space.bilibili.com/40...

Flutter 组件开发

https://space.bilibili.com/40...

Flutter Bloc

https://space.bilibili.com/40...

Flutter Getx4

https://space.bilibili.com/40...

Docker Yapi

https://space.bilibili.com/40...