Flutter是否支持负保证金?

时间:2018-01-03 23:13:56

标签: flutter

通常不需要负边距,但有些情况下它确实很有用。例如:why use negative margins?

现在,当我将容器的边距设置为负值时,我收到以下错误:

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

8 个答案:

答案 0 :(得分:17)

容器具有有用的transform属性。

enter image description here

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

答案 1 :(得分:7)

简短的回答是“不,它没有”。

为了提供更多细节,Flutter有一个复杂但有效的算法来渲染其小部件。在运行时分析边距和填充,并确定窗口小部件的最终大小和位置。当您尝试发出负边距时,您有意创建一个非valide布局,其中窗口小部件以某种方式从它应占用的空间中退出。

请考虑阅读文档here

无论如何,我相信你应该在另一个线程中更好地表达问题,并且真正问一个解决方案,看看你试图用这些负边缘实现的行为。我相信你会得到更多这样的方式。

干杯

答案 2 :(得分:7)

要回答这个问题,你首先必须确定“负利润”,或者一般来说真正的“利润”。在CSS中,边距在各种布局模型中具有不同的含义,最常见的是,它们是有助于计算块布局模型用于放置后续子节点的偏移的若干值之一;在这种情况下,负的总保证金仅仅意味着下一个孩子被放置在前一个孩子的底部之上而不是之后。

在Flutter中,与CSS一样,有几种布局模型;但是,目前没有相当于CSS块布局模型的小部件(支持边距折叠,负边距,跳过浮动等)。这样的布局模型当然可以实现,它还没有实现,至少在框架本身没有实现。

要实现这样的布局模型,您可以创建一个类似于RenderFlex或RenderListBody的RenderBox后代,可能提供了一种使用ParentDataWidget设置每个子节点边距的方法,就像Flex子节点可以使用{{1}一样。使用Expanded小部件配置。

设计这样的新布局模型时,最复杂的部分可能是决定如何处理溢出或下溢,当子项太大或太小而不适合传递给这个新布局渲染对象的约束时。如果子代下溢,RenderFlex渲染对象有一种分配空间的方法,并且如果它们溢出则认为它是错误的(在调试模式下,这由黄色和黑色条带警告区域和记录到控制台的消息显示) ;另一方面,RenderListBody渲染对象认为约束必须在主轴上无界限,这意味着您基本上只能在列表中使用此布局模型(因此名称)。

如果编写新的布局模型不具吸引力,则可以使用允许重叠子项的现有布局小部件之一。 Stack是显而易见的选择,您可以在其中设置每个子项的显式位置,并且它们可以任意重叠(这与CSS绝对位置布局模型模糊地相似)。另一个选项是CustomMultiChildLayout小部件,它允许您依次布局和定位每个子节点。有了这个,你可以一个接一个地定位每个孩子,通过将后续孩子的位置设置为从前一个孩子的大小和位置得到的值来模拟负边距,但是这样后续孩子的顶部高于前一个孩子的孩子的底部。

如果对类似块的布局模型感兴趣,我们当然可以实现它(请提交错误并描述您想要实现的模型,或者自己实现并发送拉取请求以进行审核)。但到目前为止,我们还没有发现它在实践中有用,至少没有足够的用来证明复杂性。

答案 3 :(得分:6)

我会给出一个答案,主要是因为我必须找到一种方法来做到这一点。

我想说它不理想,可能会以更好的方式完成,但它确实会产生预期的效果。

正如您所看到的,文本可以使用堆栈在其父级之外被拉负:enter image description here

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),)
        )
      ]
    )
)

答案 4 :(得分:0)

不,Flutter不允许使用负边距,但是如果万一您仍然希望小部件彼此重叠,则可以使用“已定位”堆栈,以生成负负边距的布局。

这里是一个例子:

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(),
  ));
}

这将导致:

ScreenShot

注意:您还可以在Positioned Widget中给负值。

答案 5 :(得分:0)

您可以尝试以下操作:

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?"),
                            ))
                      ]))
            ],
          ),
        ),
      ),
    );
  }
}

答案 6 :(得分:0)

您可以使用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!'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

结果:

enter image description here

答案 7 :(得分:0)

hack ,如果您真的想要这个(例如我)并且需要性能:

警告:命中测试可能在这些边缘上有问题。使用后果自负!

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));
  }
}