Does Flutter support negative margin?

Flutter

Flutter Problem Overview


Negative margin is generally not needed but there are situations where it’s really useful. For example: https://stackoverflow.com/questions/1840237/why-use-negative-margins

For now, when I set margin for a container to a negative value, I got the following error:

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

Flutter Solutions


Solution 1 - Flutter

The container has a useful transform property.

enter image description here

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

Solution 2 - Flutter

I'm gonna give an answer for this, mostly because I had to find a way to do this.

I would like to say that it is not ideal and could likely be accomplished in a better way, but it does give the desired effect.

As you can see, the text can be pulled negatively outside its parent using a stack: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),)
        )
      ]
    )
)

Solution 3 - Flutter

To answer this question you first have to define what "negative margins", or really "margins" in general, really are. In CSS, margins have various meanings in the various layout models, most commonly, they are one of several values that contribute to computing the offset that the block layout model uses to place subsequent children; a negative total margin in this case merely means the next child is placed above the bottom of the previous child instead of after it.

In Flutter, as in CSS, there are several layout models; however, there is currently no widget that is equivalent to the CSS block layout model (which supports margin collapsing, negative margins, skipping floats, etc). Such a layout model could certainly be implemented, it just hasn't been implemented yet, at least not in the framework itself.

To implement such a layout model, you would create a RenderBox descendant similar to RenderFlex or RenderListBody, probably providing a way to set the margins of each child using a ParentDataWidget in the same way that Flex children can have their flex configured using the Expanded widget.

Probably the most complicated part of designing a new layout model like this would be deciding how to handle overflow or underflow, when the children are too big or too small to fit the constraints passed to this new layout render object. The RenderFlex render object has a way to distribute the space if the children underflow, and considers it an error if they overflow (in debug mode, this is shown by a yellow-and-black striped warning area and a message logged to the console); the RenderListBody render object on the other hand takes the view that the constraints must be unbounded in the main axis, which means you can basically only use this layout model inside a list (hence the name).

If writing a new layout model is not attractive, you could use one of the existing layout widgets that allow overlapping children. Stack is the obvious choice, where you set the explicit positions of each child and they can overlap arbitrarily (this is vaguely similar to the CSS absolute position layout model). Another option is the CustomMultiChildLayout widget, which lets you layout and position each child in turn. With this, you could position each child one after the other, simulating negative margins by setting the position of the subsequent child to a value that's derived from the size and position of the previous child, but such that the subsequent child's top is above the previous child's bottom.

If there's interest in a block-like layout model, we could certainly implement it (please file a bug and describe the model you'd like implemented, or, implement it yourself and send a pull request for review). So far, though, we've not found that it has been that useful in practice, at least not useful enough to justify the complexity.

Solution 4 - Flutter

The short answer is "No, it doesn't".

To give few more details, Flutter has a sophisticated but effective algorithm for rendering its widgets. Margins and Paddings are analyzed at runtime, and the final size and position of the widget is determined. When you try to issue a negative margine you are purposefully creating a not valide layout where a widget is somehow dropping out of the space it is supposed to occupy.

Consider reading the doc here.

Anyhow I believe you should formulate better the question in another thread and really ask a solution for the behavior you are trying to achieve with those negative margins. I am sure you'll get much more that way.

Cheers

Solution 5 - Flutter

No, Flutter does not allow negative margins but just in case you still want your widgets to overlap each other, you can use a Stack with Positioned which will allow you to generate the layout which you can do with negative margins.

Here is an example :

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

This will result in :

ScreenShot

NOTE: You can also give negative values in Positioned Widget.

Solution 6 - Flutter

You can use OverflowBox to disregard certain constraints.

  @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!'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

Result:

enter image description here

Solution 7 - Flutter

A hack if you really want this (for example, me) and need performance:

Disadvantage: The hit testing has problem on those edges. But if you only want to display the widget without wanting to click it, it is completely fine.

How to use it: As if you are using Padding widget, except that now your padding can be negative and no errors will happen.

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

Solution 8 - Flutter

You can try something like this:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionWei SongView Question on Stackoverflow
Solution 1 - FlutterPraveen VijayanView Answer on Stackoverflow
Solution 2 - FlutterAntonBView Answer on Stackoverflow
Solution 3 - FlutterIan HicksonView Answer on Stackoverflow
Solution 4 - FlutterFabio VeroneseView Answer on Stackoverflow
Solution 5 - FlutterSarthak VermaView Answer on Stackoverflow
Solution 6 - FlutterTom RoggeroView Answer on Stackoverflow
Solution 7 - Flutterch271828nView Answer on Stackoverflow
Solution 8 - FlutterJack SunView Answer on Stackoverflow