Flutter

Flutter - 即時取得和應用子元件尺寸的方法

潘士凱 Shikai Pan 2025/02/07 11:44:38
16

 

在 Flutter 開發中,我們經常需要根據子元件的大小來動態調整 UI 或觸發特定事件。

本文章將透過一個實際案例,展示如何有效地解決這類問題。


實際場景

想像一下這樣的一個設計需求:「輸入框的邊框(border),在單行的時候要圓角,多行時 radius則是 16。」

作為 Flutter的開發者,我們知道邊框是透過 TextFieldTextFormFielddecoration進行設定,但在創建 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));
      }
    }
  }
}
  • 原理說明

    1. WidgetSizeObserverRenderObject 繼承自 RenderProxyBox,它能夠取得子元件的佈局資訊(ChileType)。

    2. WidgetSizeObserverRenderObject.performLayout 方法是關鍵,它在每次佈局後檢查子元件的大小是否發生變化。

    3. 如果尺寸變化了,透過 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 來觀察/監測子元件的大小變化,如此一來,我們便可以根據高度來動態調整邊框的圓角半徑。

 

總結

通過這個實例,我們不僅解決了設計師的特定需求,還創建了一個可重用的元件來處理子元件大小變化的場景。

這種方法具有以下優點:

  1. 靈活性:可以輕鬆應用於各種需要響應子元件大小變化的場景。

  2. 性能:比起使用 TextPainter 等方法,這種方式更加高效。

  3. 可維護性:代碼結構清晰,易於理解和擴展。

 

未來有機會的話,我們可以深入探討 RenderObject 的工作原理,或者討論如何優化這個元件以處理更複雜的場景。

謝謝你的觀看!

潘士凱 Shikai Pan