Old iron remember to forward, Brother Mao will present more Flutter good articles~~~~
WeChat flutter training group ducafecat
original
https://rlesovyi.medium.com/writing-custom-widgets-in-flutter-part-1-ellipsizedtext-a0efdc1368a8
Code
https://github.com/MatrixDev/Flutter-CustomWidgets
text
The declarative user interface in Flutter is pretty good, easy to use, and it is very tempting to use as much as possible. But many times, developers just go too far-write everything in a declarative way, even if sometimes tasks can be more effective and easier to understand in a more mandatory way.
Everyone should understand-there must be a balance between declarative and imperative programming. Each method has its own purpose, and each method is better than others for certain tasks.
In this series of articles, I will describe how to solve different problems by creating custom widgets from scratch. Each one is slightly more complicated than the previous one.
Thinking
Before looking at the code, we need to know some basic things.
Widget ー is just an immutable (preferably const) class, which contains the configuration properties of Elements and RenderObjects. It is also responsible for creating the aforementioned elements and rendering objects. The important thing to understand-widgets never contain state or any business logic, just pass them on.
Elements are the entities responsible for the actual UI tree. It contains references to all child elements, and (unlike Widget) references to its parent element. Elements will be reused in most cases, unless the keys or widgets are changed. Therefore, if the onlyWidget property is changed, the Element will remain unchanged even if a new Widget is assigned.
State ー is just a user-defined class inside Element, and it also exposes some callbacks from it.
RenderObject ー is responsible for the actual size calculation, the placement and drawing of sub-elements, the handling of touch events, etc. These objects are very similar to the classic view of Android or other frameworks.
Why do we have elements and render objects at the same time? Because of high efficiency. Each widget has its own elements, but only some have rendering objects. Because of this, many layout, touch and other hierarchy traversal calls can be omitted.
Code
The first example is a very simple widget that uses ellipsis to scale the text when the text does not fit. Why do we need such a widget when the built-in text has ellipsis support, you may ask? The answer is simple ---- so far, it has only expressed through words instead of characters https://github.com/flutter/flutter/issues/18761. So if you have a very long word at the end-most of the time you can only see the first few letters of the word, even if there is enough space to fill it.
So, let's get started. Flutter has many built-in base classes and mixins that will help build completely custom widgets. Here are some of them:
- LeafRenderObjectWidget has no child
- SingleChildRenderObjectWidget a child
- MultiChildRenderObjectWidget multiple child
In our example, we will use LeafRenderObjectWidget, because we only need to render text and will not have child nodes:
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;
}
}
We created our Widget, the only unusual thing is that there are two ways:
- createRenderObject — responsible for actually creating our RenderObject
- updateRenderObject — When the data of the Widget changes but the RenderObject remains unchanged, updateRenderObject will be called. In this case, we need to update the data in the RenderObject, otherwise it will render the old text
I also need to note that copying every value from the widget to the RenderObject is preferred. But I will go through the entire Widget because they are immutable anyway (and I am too lazy to write all the boilerplate code).
Now let's start with the actual rendered object:
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();
}
}
Here, we defined all the variables and wrote a setter to actually update them. There is also a safeguard to check whether the value has actually changed-if there is no change, there is no need to recalculate the ellipsis and redraw the text.
Now we need to lay out the rendering objects.
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,
);
}
}
The layout process is quite simple. All we need to do-calculate the size of the rendered object based on the constraints provided to us. Constraints only describe the minimum and maximum scales that we must comply with. In addition, if there is no change, and the size has been calculated during the previous layout transfer, an additional check is added.
The actual process of creating ellipsis text is quite cumbersome, and there must be a better solution, but I chose to use binary search to find the best match.
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));
}
}
I won't finish all this logic (you can read through it if you want). But the important thing is that TextPainter is used to calculate the size of the text. If the text size is longer than our constraint, I will try to make it shorter and shorter until it meets our constraint.
_layoutText
used to calculate the size of our cropped text:
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;
}
That's almost it, all we have to do is-actually draw our text.
@override
void paint(PaintingContext context, Offset offset) {
_textPainter.paint(context.canvas, offset);
}
© Cat brother
Past
Open source
GetX Quick Start
https://github.com/ducafecat/getx_quick_start
News client
https://github.com/ducafecat/flutter_learn_news
strapi manual translation
WeChat discussion group ducafecat
Series collection
Translation
https://ducafecat.tech/categories/%E8%AF%91%E6%96%87/
Open source project
https://ducafecat.tech/categories/%E5%BC%80%E6%BA%90/
Dart programming language basics
https://space.bilibili.com/404904528/channel/detail?cid=111585
Getting started with Flutter zero foundation
https://space.bilibili.com/404904528/channel/detail?cid=123470
Flutter actual combat from scratch news client
https://space.bilibili.com/404904528/channel/detail?cid=106755
Flutter component development
https://space.bilibili.com/404904528/channel/detail?cid=144262
Flutter Bloc
https://space.bilibili.com/404904528/channel/detail?cid=177519
Flutter Getx4
https://space.bilibili.com/404904528/channel/detail?cid=177514
Docker Yapi
https://space.bilibili.com/404904528/channel/detail?cid=130578
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。