老鐵記得 轉發 ,貓哥會呈現更多 Flutter 好文~~~~
微信 flutter 研修群 ducafecat
原文
程式碼
正文
宣告式使用者介面在 Flutter 是相當不錯,易於使用,它是非常誘人的使用盡可能。但是很多時候,開發人員只是做得太過火了ーー用宣告的方式編寫所有東西,即使有時候任務可以以更強制性的方式更有效、更容易理解。
每個人都應該明白的---- 在陳述和指令式程式設計之間必須有一個平衡。每種方法都有自己的用途,每種方法在某些任務上都比其他方法更加出色。
在本系列文章中,我將描述如何通過從頭建立自定義小部件來解決不同的問題。每一個都比前一個稍微複雜一點。
思考
在檢視程式碼之前,我們需要知道一些基本的事情。
Widget ー只是一個不可變的(最好是 const)類,它包含 Elements 和 RenderObjects 的配置屬性。它還負責建立上述元素和渲染物件。需要理解的重要事情ー小部件從不包含狀態或任何業務邏輯,只是傳遞它們。
元素ー是負責實際 UI 樹的實體。它包含對所有子元素的引用,以及(不像 Widget)對其父元素的引用。元素在大多數情況下都會被重用,除非鍵或小部件被更改。因此,如果 onlyWidget 屬性被更改,即使分配了新的 Widget,Element 也將保持不變。
State ー只不過是 Element 內部的一個使用者定義類,它還公開了一些來自它的回撥。
RenderObject ーー負責實際尺寸的計算、子元素的放置、繪製、觸控事件的處理等。這些物件與 Android 或其他框架的經典檢視非常相似。
為什麼我們同時擁有元素和渲染物件?因為效率高。每個小部件都有各自的元素,但只有一些有渲染物件。由於這一點,很多佈局,觸控和其他層次遍歷呼叫可以省略。
程式碼
第一個例子是一個非常簡單的小部件,它在文字不適合時用省略號縮放文字。為什麼我們需要這樣一個小部件時,內建的文字已經省略號支援你可能會問?答案很簡單---- 到目前為止,它只是通過文字而不是字元來表達 github.com/flutter/flu…
那麼,我們開始吧。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;
}
複製程式碼
差不多就是這樣了,我們剩下要做的就是——實際上畫出我們的文字。
@override
void paint(PaintingContext context, Offset offset) {
_textPainter.paint(context.canvas, offset);
}
複製程式碼
© 貓哥
往期
開源
GetX Quick Start
新聞客戶端
strapi 手冊譯文
微信討論群 ducafecat
系列集合
譯文
開源專案
Dart 程式語言基礎
space.bilibili.com/404904528/c…
Flutter 零基礎入門
space.bilibili.com/404904528/c…
Flutter 實戰從零開始 新聞客戶端
space.bilibili.com/404904528/c…
Flutter 元件開發
space.bilibili.com/404904528/c…
Flutter Bloc
space.bilibili.com/404904528/c…
Flutter Getx4
space.bilibili.com/404904528/c…