😔

[Flutter] ContainerとDecoratedBoxのBoxDecoration.borderの違いについて

に公開1

FlutterでContainer.decorationDecoratedBox.decorationBoxDecoration.borderに微妙な違いがあったので、紹介しておきます。

BoxDecoration.borderについて

BoxDecoration.borderは、子Widgetに枠線を表示させます。以下のように書くと、枠線が表示されます。

Container(
  decoration: BoxDecoration(
    border: Border.all(),
  ),
  child: const Text('枠線が表示されます'),
),

枠線はContainer.decoration、またはDecoratedBox.decorationBoxDecoration.borderを指定することで表示できます。

Linterからの指摘

実は、先程のコードだとLinterから指摘を受けます。それがこちらです。
https://dart-lang.github.io/linter/lints/use_decorated_box.html

Containerの中身が子以外にdecorationのみの場合、DecoratedBoxを使用することが推奨されます。Containerはconstコンストラクタを持たないため、むやみに使うとパフォーマンス的によろしくないのです。そのため、constコンストラクタを持つDecoratedBoxが推奨されるのですね。

// 引数が子以外にdecorationのみの場合はLinterによって指摘される
Container(
  decoration: BoxDecoration(
    border: Border.all(),
  ),
  child: const Text('枠線が表示されます'),
),

// Linterでこちらを推奨される
DecoratedBox(
  decoration: BoxDecoration(
    border: Border.all(),
  ),
  child: const Text('枠線が表示されます'),
),

描画の違いについて

私はLinterに従い、もともとContainerにしていた箇所をDecoratedBoxに置き換えていました。しかし置き換えてアプリをビルドしてみると、描画に微妙な違いがあったのです。

それがこちら↓
※分かりやすいよう枠線を太くしています。

Containerでは子Widgetの外側に枠線が入るのに対し、DecoratedBoxでは子Widgetの内側に枠線が入ります。コードは以下の通りです。

return Column(
  children: [
    Container(
      decoration: BoxDecoration(
        border: Border.all(
          width: 10,
        ),
      ),
      child: const Text(
        '枠線が表示されます',
        style: TextStyle(fontSize: 50),
      ),
    ),
    
    const SizedBox(height: 20),
            
    DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(
          width: 10,
        ),
      ),
      child: const Text(
        '枠線が表示されます',
        style: TextStyle(fontSize: 50),   
      ),
    ),
  ],
),

解決策

Containerはなるべく使いたくない!」「Linterの通りにしたい!」という人は、DecoratedBoxの子にPaddingを置きましょう。Borderの幅と同じ値をPaddingで設定すれば、Containerを使った時と同じ見た目になります。

書き換えたコードはこちらです

const _borderWidth = 10.0;
return Column(
  children: [
    Container(
      decoration: BoxDecoration(
        border: Border.all(
          width: 10,
        ),
      ),
      child: const Text(
        '枠線が表示されます',
        style: TextStyle(fontSize: 50),
      ),
    ),
    
    const SizedBox(height: 20),
            
    DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(
          width: _borderWidth,
        ),
      ),
      // Paddingを挟み、Borderと同じ値を入れる
      child: const Padding(
        padding: EdgeInsets.all(_borderWidth),
	child: Text(
	  '枠線が表示されます',
          style: TextStyle(fontSize: 50),   
	),
      ),
    ),
  ],
),

以上になります。
Linterで推奨するなら、DecoratedBoxだけで同じ描画をしろ!って話ですよね。
最後までご覧いただき、ありがとうございました。

Flutter勉強中の身です。何か間違いがありましたらコメントいただけると幸いです。

Discussion

づだづだ

コメント失礼します!
少し補足させていただきます!

解決策として _borderWidthPadding に渡すという方法を取られている点についてです!

DecoratedBox(
  decoration: BoxDecoration(
    border: Border.all(
      width: _borderWidth,
    ),
  ),
  // Paddingを挟み、Borderと同じ値を入れる
  child: const Padding(
    padding: EdgeInsets.all(_borderWidth),
      child: Text(
        '枠線が表示されます',
        style: TextStyle(fontSize: 50),   
      ),
   ),
),

この方法は、EdgeInsets.all では対応可能ですが、.symmetric.only などの対応が難しいです!
なので、Container に余白が生まれる理由と同じアプローチをとることで、これらを解決する方法を提案します!

final decoration = BoxDecoration(
  border: Border.all(width: _borderWidth),
);

DecoratedBox(
  decoration: decoration,
  child: const Padding(
+  padding: decoration.padding,
      child: Text(
        '枠線が表示されます',
        style: TextStyle(fontSize: 50),   
      ),
   ),
),

Decoration.padding


EdgeInsetsGeometry get padding => border?.dimensions ?? EdgeInsets.zero;

Container で padding が適応されている理由

https://github.com/flutter/flutter/blob/da48b34f50a86aa8149f102cd3f5d84656d8fb9e/packages/flutter/lib/src/widgets/container.dart#L364

Decoration からPadding を取得している箇所

  EdgeInsetsGeometry? get _paddingIncludingDecoration {
    return switch ((padding, decoration?.padding)) {
      (null, final EdgeInsetsGeometry? padding) => padding,
      (final EdgeInsetsGeometry? padding, null) => padding,
      (_) => padding!.add(decoration!.padding),
    };
  }