How to Create the Negative Margin Effect in Flutter

Written by pmatatias | Published 2023/05/03
Tech Story Tags: flutter-app-development | flutter | dart | flutter-app | css | reactjs | programming | responsive-web-design

TLDRA card widget for a web dashboard with React JS can be recreated with Flutter. In original source, the creator uses a negative margin for the icon box. In Flutter, we can use an absolute position and a negative value in the `top` property.via the TL;DR App

About 2 years ago, I created a sample card widget for a web dashboard with React JS. I was inspired by the source code on Codepen. Now, I want to recreate it with Flutter code. Since it’s made for web UI, the style uses CSS code. In the original source, the creator uses a negative margin for the icon box.

 .card
    margin: 0 -8px 32px -8px
    color: rgba(0,0,0,.87)
    box-shadow: 2px 2px 8px rgba(0,0,0,.05)

I’ve recreated a similar card according to my dashboard needs with React JS. I use an absolute position and a negative value in the top property.

Look at the icons stacked on top of the cards.

boxicon: {
    position: "absolute",
    zIndex: 1,
    top: -20,
    left: 12,
}

How about in Flutter?

In Flutter, we can use margin on some of the widgets. For example, Container, Card, etc. And the margin property is implementing the EdgeInsetsGeometry class. This class is also used to set padding in Flutter.

How we use padding and margin in Flutter:

Container(
   padding: EdgeInsets.all(8),
   margin: EdgeInsets.fromLRTB(4,8,4,2)
)

EdgeInsetsGeometry in Flutter, has an isNonNegative method. So….

Margin values must be positive, we cannot assign negative values to EdgeInsetsGeometry.

We will receive a failed assertion when we provide negative values to the margins or padding properties.

════════ Exception caught by widgets library ════════
The following assertion was thrown building Card(dirty, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#95d0d]]):
'package:flutter/src/widgets/container.dart': Failed assertion: line 267 pos 15: 'margin == null || margin.isNonNegative': is not true.


To create the card widget, we can make a similar behavior, or create a similar shape.

Here are several ways to achieve this:

  • Positioned widget | In CSS if you know about position: absolute. In Flutter we can achieve similar behavior with the Positioned widget.

  • Transform widget | This is similar to the transform property in CSS. It applies a transformation before the widget is rendered.

  • CustomPainter | I haven’t tried this, but I think it’s possible to create a custom shape like the card above with the CustomPainter class.

In this article, I will use the Transform class to crate card widgets like the image above. Since a negative margin can’t be applied, we will move the icon widget with matrix translation from the Transform class.

Matrix translation: type of transformation that occurs when a figure is moved from one location to another on the coordinate plane without changing its size, shape, or orientation… [ref]

With matrix translation, we can move widgets without changing anything except their position. We can prove it by comparing the position of the widget’s coordinates. If you wonder how to get the coordinate of a widget, you can try with extension method globalPaintBound.

Read my article about it here:

https://medium.easyread.co/how-to-get-widget-coordinates-in-flutter-ui-dart-extension-4-d59dc15a9e3f?embedable=true

Making cards

Before we apply the transformation widget, let's create the initial card. (See the picture before translation).

Now we want to place the icon staked above the card. We will use matrix translation. To add the translation, there are 2 options.

  • If the widget parent is not a Container widget, you can wrap them with Transfrom the widget. For example:

    Transform(
      transform: Matrix4.translationValues(x, y, z),
      child: const SomeWidget(),
    )
    

  • If it's a Container, then we can assign it directly to the transform property:

    Container(
      transform: Matrix4.translationValues(x, y, z),
      .....// other props
    )
    

After adding the translation value to the icon widget, now we have a stacked Icon on the card widget (See the picture after translation).

Additional: Neumorphism widget

We have done with the shape of the card widget. But if we compare the current result, we missed the shadow. To add the effect shadow we can add it manually to the BoxDecoration property from the Container widget.

We also can use an extension method named addNeumorphism .

  • Manual shadow for the icon box

    Container(
      decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20),
          boxShadow: const [
            BoxShadow(
              offset: Offset(3, 3),
              blurRadius: 8,
              color: Color(0xFFffa726),
            ),
            BoxShadow(
              offset: Offset(-1, -1),
              blurRadius: 10,
              color: Colors.white,
            ),
    ....
    

  • Use the extension method neumorphism

    @override
    Widget build(BuildContext context) {
      return Container(
      ..../// rest of code
      ).addNeumorphism(
          bottomShadowColor: Colors.black26,
    );
    

And now we have a better card with the implemented shadow effect:

Finishing Up

The final step we need to do is to create a responsive layout on the card widget. I’ve also shared an article about responsive layouts in Flutter. For more details, you can read it here:

https://hackernoon.com/creating-responsive-flutter-app-layouts-for-all-screen-sizes?embedable=true

Cards will be displayed with the GridView.count() widget. Then we apply the responsive method to determine the crossAxisCount, and the childAspectRatiovalue.

...
body: Padding(
  padding: const EdgeInsets.symmetric(horizontal: 18.0),
  child: GridView.count(
    crossAxisSpacing: 20,
    // mainAxisSpacing: 20,
    childAspectRatio: size.width /
        size.height /
        context.responsive(0.3,
            md: 0.6,
            lg: 0.59,
            xl: 1.2), // this ratio is hard code, you may need to change it base on your need
    crossAxisCount: context.responsive(1, md: 2, xl: 4),
    children: widge,
  ),
),
...

And finally, we have a card widget with stacked icons written in Flutter code.

Full code and demo can be found on Dartpad: https://dartpad.dev/?id=be8f84a08f9e2347f20be108ac030484

Thank you for reading!


Also published here.


Written by pmatatias | Dart make my heart flutter
Published by HackerNoon on 2023/05/03