Flutter - 即時取得和應用子元件尺寸的方法
在 Flutter 開發中,我們經常需要根據子元件的大小來動態調整 UI 或觸發特定事件。
本文章將透過一個實際案例,展示如何有效地解決這類問題。
實際場景
想像一下這樣的一個設計需求:「輸入框的邊框(border),在單行的時候要圓角,多行時 radius則是 16。」
作為 Flutter的開發者,我們知道邊框是透過 TextField
與 TextFormField
的 decoration
進行設定,但在創建 InputDecoration
的當下,我們無法即時的知道輸入的文字是單行或是多行。
你也許會想到使用 TextPainter
來計算文字的尺寸,再依據結果設定 InputDecoration.border
,但這種方法較為複雜且難以重用。
那麼,有沒有更簡潔、更通用的解決方案呢?
解決思路
我們反過來,不設定文字輸入框的邊框(Border.none),接著創建一個容器元件,它能夠監測子元件的大小變化,並依此動態地調整自身的邊框樣式。
這種方法不僅解決了當前的問題,還可以應用於其他類似場景。
核心元件: WidgetSizeObserver
typedef OnWidgetSizeChanged = void Function(Size size);
class WidgetSizeObserver extends SingleChildRenderObjectWidget {
const WidgetSizeObserver({
super.key,
required this.onChanged,
required super.child,
});
final OnWidgetSizeChanged onChanged;
@override
RenderObject createRenderObject(BuildContext context) =>
WidgetSizeObserverRenderObject(onChanged);
@override
void updateRenderObject(
BuildContext context,
WidgetSizeObserverRenderObject renderObject,
) {
renderObject.onChanged = onChanged;
}
}
class WidgetSizeObserverRenderObject extends RenderProxyBox {
WidgetSizeObserverRenderObject(this.onChanged);
OnWidgetSizeChanged onChanged;
Size? _oldSize;
@override
void performLayout() {
super.performLayout();
if (child != null) {
final newSize = child!.size;
if (_oldSize != newSize) {
_oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) => onChanged(newSize));
}
}
}
}
-
原理說明
-
WidgetSizeObserverRenderObject
繼承自RenderProxyBox
,它能夠取得子元件的佈局資訊(ChileType
)。 -
WidgetSizeObserverRenderObject.performLayout
方法是關鍵,它在每次佈局後檢查子元件的大小是否發生變化。 -
如果尺寸變化了,透過
WidgetsBinding.instance.addPostFrameCallback
來確保在當前幀渲染完成後才觸發 callback,避免可能的佈局循環。
-
應用:AdaptiveRoundedBorder
class AdaptiveRoundedBorder extends StatefulWidget {
const AdaptiveRoundedBorder({
super.key,
required this.child,
});
final Widget child;
@override
State<AdaptiveRoundedBorder> createState() => _AdaptiveRoundedBorderState();
}
class _AdaptiveRoundedBorderState extends State<AdaptiveRoundedBorder> {
Size? size;
BorderRadiusGeometry? get borderRadius {
if (size == null) return null;
return BorderRadius.circular(size!.height <= 32 ? 100 : 16);
}
BoxBorder? get border {
return Border.all(
color: Colors.grey.shade200,
width: 1,
);
}
@override
Widget build(BuildContext context) {
return WidgetSizeObserver(
onChanged: (size) {
setState(() {
this.size = size;
});
},
child: Container(
decoration: BoxDecoration(
borderRadius: borderRadius,
border: border,
),
child: widget.child,
),
);
}
}
使用 WidgetSizeObserver
來觀察/監測子元件的大小變化,如此一來,我們便可以根據高度來動態調整邊框的圓角半徑。
總結
通過這個實例,我們不僅解決了設計師的特定需求,還創建了一個可重用的元件來處理子元件大小變化的場景。
這種方法具有以下優點:
-
靈活性:可以輕鬆應用於各種需要響應子元件大小變化的場景。
-
性能:比起使用
TextPainter
等方法,這種方式更加高效。 -
可維護性:代碼結構清晰,易於理解和擴展。
未來有機會的話,我們可以深入探討 RenderObject
的工作原理,或者討論如何優化這個元件以處理更複雜的場景。
謝謝你的觀看!