簡體   English   中英

Flutter 支持負邊距嗎?

[英]Does Flutter support negative margin?

通常不需要負邊距,但在某些情況下它確實有用。 例如: 為什么使用負邊距?

現在,當我將容器的邊距設置為負值時,出現以下錯誤:

I/flutter ( 3173): 'package:flutter/src/widgets/container.dart': Failed assertion: line 251: 'margin == null ||
I/flutter ( 3173): margin.isNonNegative': is not true.

容器有一個有用的變換屬性。

在此處輸入圖像描述

child: Container(
  color: Theme.of(context).accentColor,
  transform: Matrix4.translationValues(0.0, -50.0, 0.0),
),

我會為此給出一個答案,主要是因為我必須找到一種方法來做到這一點。

我想說這並不理想,可能會以更好的方式完成,但它確實產生了預期的效果。

如您所見,可以使用堆棧將文本負向拉到其父級之外: 在此處輸入圖像描述

Container(
  constraints: BoxConstraints.loose(Size.fromHeight(60.0)),
  decoration: BoxDecoration(color: Colors.black), 
  child: 
    Stack(
      alignment: Alignment.topCenter,
      overflow: Overflow.visible,
      children: [
        Positioned(
          top: 10.0,
          left: -15.0,
          right: -15.0,
          child: Text("OUTSIDE CONTAINER", style: TextStyle(color: Colors.red, fontSize: 24.0),)
        )
      ]
    )
)

要回答這個問題,您首先必須定義什么是“負邊距”,或者通常是真正的“邊距”。 在 CSS 中,邊距在各種布局模型中具有不同的含義,最常見的是,它們是有助於計算塊布局模型用於放置后續子項的偏移量的幾個值之一; 在這種情況下,負的總邊距僅意味着下一個孩子被放置在前一個孩子的底部之上,而不是在它之后。

在 Flutter 中,和在 CSS 中一樣,有幾種布局模型; 但是,目前沒有與 CSS 塊布局模型等效的小部件(支持邊距折疊、負邊距、跳過浮動等)。 這樣的布局模型當然可以實現,只是還沒有實現,至少框架本身還沒有實現。

要實現這樣的布局模型,您將創建一個類似於 RenderFlex 或 RenderListBody 的 RenderBox 后代,可能提供一種使用 ParentDataWidget 設置每個孩子的邊距的方法,就像 Flex 孩子可以使用 Expanded 小部件配置其flex一樣。

設計一個像這樣的新布局模型最復雜的部分可能是決定如何處理溢出或下溢,當孩子太大或太小而無法適應傳遞給這個新布局渲染對象的約束時。 如果子項下溢,RenderFlex 渲染對象有一種分配空間的方法,如果它們溢出,則認為這是一個錯誤(在調試模式下,這由黃黑條紋警告區域和記錄到控制台的消息顯示) ; 另一方面,RenderListBody 渲染對象認為約束在主軸上必須是無界的,這意味着您基本上只能在列表中使用此布局模型(因此得名)。

如果編寫新的布局模型沒有吸引力,您可以使用允許重疊子級的現有布局小部件之一。 堆棧是顯而易見的選擇,您可以在其中設置每個孩子的顯式位置,並且它們可以任意重疊(這與 CSS 絕對位置布局模型隱約相似)。 另一個選項是 CustomMultiChildLayout 小部件,它可以讓您依次布局和定位每個孩子。 有了這個,您可以一個接一個地定位每個孩子,通過將后續孩子的位置設置為從前一個孩子的大小和位置派生的值來模擬負邊距,但這樣后續孩子的頂部高於前一個孩子的屁股。

如果對塊狀布局模型感興趣,我們當然可以實現它(請提交錯誤並描述您想要實現的模型,或者自己實現它並發送拉取請求以供審查)。 不過,到目前為止,我們還沒有發現它在實踐中那么有用,至少沒有有用到足以證明復雜性的合理性。

簡短的回答是“不,它沒有”。

為了提供更多細節,Flutter 有一個復雜但有效的算法來渲染它的小部件。 Margins 和 Paddings 在運行時進行分析,最終確定小部件的大小和位置。 當您嘗試發出負邊距時,您是在故意創建一個無效的布局,其中小部件以某種方式從它應該占用的空間中退出。

考慮在這里閱讀文檔。

無論如何,我認為您應該在另一個線程中更好地提出問題,並真正為您試圖通過這些負邊距實現的行為提出解決方案。 我相信你會得到更多這樣的。

干杯

不,Flutter 不允許負邊距,但萬一您仍然希望小部件相互重疊,您可以使用帶有 Positioned 的 Stack,這將允許您生成可以使用負邊距的布局。

這是一個例子:

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  MyHomePageState createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage>  {


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: new Center(
          child: new Container(
            padding: const EdgeInsets.all(8.0),
            height: 500.0,
            width: 500.0,
            child: new Stack(
              overflow: Overflow.visible,
              children: <Widget>[
                new Icon(Icons.pages, size: 36.0, color: Colors.red),
                new Positioned(
                  left: 20.0,
                  child: new Icon(Icons.pages, size: 36.0, color: Colors.green),
                ),

              ],
            ),
          ),
    )
    );
  }
}

void main() {
  runApp(new MaterialApp(
    title: 'Flutter Demo',
    theme: new ThemeData(
      primarySwatch: Colors.deepPurple,
    ),
    home: new MyHomePage(),
  ));
}

這將導致:

截屏

注意:您也可以在 Positioned Widget 中給出負值。

您可以使用OverflowBox忽略某些約束。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.blue.shade300,
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              children: [
                Expanded(
                  child: Container(
                    color: Colors.white,
                    child: Center(
                      child: Text('Padding on this one.'),
                    ),
                  ),
                ),
                SizedBox(height: 20),
                Expanded(
                  child: OverflowBox(
                    maxWidth: MediaQuery.of(context).size.width,
                    child: Container(
                      color: Colors.red.shade300,
                      child: Center(
                        child: Text('No padding on this one!'),
                      ),
                    ),
                  ),
                ),
                SizedBox(height: 20),
                Expanded(
                  child: Container(
                    color: Colors.yellow.shade300,
                    child: Center(
                      child: Text('Look, padding is back!'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

結果:

在此處輸入圖像描述

如果您真的想要這個(例如,我)並且需要性能,請使用hack

缺點:命中測試在這些邊緣有問題。 但是,如果您只想顯示小部件而不想單擊它,那完全沒問題。

如何使用它:就像您使用 Padding 小部件一樣,除了現在您的填充可以是負數並且不會發生錯誤。

import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class AllowNegativePadding extends SingleChildRenderObjectWidget {
  const AllowNegativePadding({
    Key key,
    @required this.padding,
    Widget child,
  })  : assert(padding != null),
        super(key: key, child: child);

  /// The amount of space by which to inset the child.
  final EdgeInsetsGeometry padding;

  @override
  RenderAllowNegativePadding createRenderObject(BuildContext context) {
    return RenderAllowNegativePadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderAllowNegativePadding renderObject) {
    renderObject
      ..padding = padding
      ..textDirection = Directionality.of(context);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
  }
}

class RenderAllowNegativePadding extends RenderShiftedBox {
  RenderAllowNegativePadding({
    EdgeInsetsGeometry padding,
    TextDirection textDirection,
    RenderBox child,
  })  : assert(padding != null),
        // assert(padding.isNonNegative),
        _textDirection = textDirection,
        _padding = padding,
        super(child);

  EdgeInsets _resolvedPadding;

  void _resolve() {
    if (_resolvedPadding != null) return;
    _resolvedPadding = padding.resolve(textDirection);
    // assert(_resolvedPadding.isNonNegative);
  }

  void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
  }

  /// The amount to pad the child in each dimension.
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
  /// must not be null.
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;

  set padding(EdgeInsetsGeometry value) {
    assert(value != null);
    // assert(value.isNonNegative);
    if (_padding == value) return;
    _padding = value;
    _markNeedResolution();
  }

  /// The text direction with which to resolve [padding].
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;

  set textDirection(TextDirection value) {
    if (_textDirection == value) return;
    _textDirection = value;
    _markNeedResolution();
  }

  @override
  double computeMinIntrinsicWidth(double height) {
    _resolve();
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
    if (child != null) // next line relies on double.infinity absorption
      return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
    return totalHorizontalPadding;
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    _resolve();
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
    if (child != null) // next line relies on double.infinity absorption
      return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
    return totalHorizontalPadding;
  }

  @override
  double computeMinIntrinsicHeight(double width) {
    _resolve();
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
    if (child != null) // next line relies on double.infinity absorption
      return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
    return totalVerticalPadding;
  }

  @override
  double computeMaxIntrinsicHeight(double width) {
    _resolve();
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
    if (child != null) // next line relies on double.infinity absorption
      return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
    return totalVerticalPadding;
  }

  @override
  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    _resolve();
    assert(_resolvedPadding != null);
    if (child == null) {
      size = constraints.constrain(Size(
        _resolvedPadding.left + _resolvedPadding.right,
        _resolvedPadding.top + _resolvedPadding.bottom,
      ));
      return;
    }
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
    child.layout(innerConstraints, parentUsesSize: true);
    final BoxParentData childParentData = child.parentData as BoxParentData;
    childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
    size = constraints.constrain(Size(
      _resolvedPadding.left + child.size.width + _resolvedPadding.right,
      _resolvedPadding.top + child.size.height + _resolvedPadding.bottom,
    ));
  }

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
      final Rect outerRect = offset & size;
      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
      return true;
    }());
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
  }
}

你可以嘗試這樣的事情:

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
    ));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('text'),
      ),
      body: Container(
        child: Center(
          child: Column(
            children: <Widget>[
              Container(
                  height: 300.0,
                  width: MediaQuery.of(context).size.width,
                  decoration: BoxDecoration(
                      image: DecorationImage(
                          image: NetworkImage(
                              "https://images.unsplash.com/photo-1539450780284-0f39d744d390?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d30c5801b9fff3d4a5b7f1522901db9f&auto=format&fit=crop&w=1051&q=80"),
                          fit: BoxFit.cover)),
                  child: Stack(
                      alignment: Alignment.topCenter,
                      overflow: Overflow.visible,
                      children: [
                        Positioned(
                            top: 200.0,
                            child: Card(
                              child: Text("Why not?"),
                            ))
                      ]))
            ],
          ),
        ),
      ),
    );
  }
}

要擴展接受的答案,您可以使用Transform.translate包裝任何小部件。 它需要一個簡單的Offset作為參數。

我發現它比翻譯矩陣更容易使用。

Transform.translate(
   // e.g: vertical negative margin
   offset: const Offset(-10, 0),
   child: ...
),

要克服一些水平填充,您可以創建這樣的小部件:

用法(將從左右填充中取出 8pt。

const ExpandWidth(
  child: MyWidget(),
  width: 8,
)

執行:

class ExpandWidth extends StatelessWidget {
  final double width;
  final Widget child;

  const ExpandWidth({
    super.key,
    required this.child,
    this.width = 0,
  });

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        return IntrinsicHeight(
          child: OverflowBox(
            maxWidth: constraints.maxWidth + width * 2,
            child: child,
          ),
        );
      },
    );
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM