Sneh Pandya Exploring the horizon with a knack for product management. Co-host of the NinjaTalks podcast and community organizer at Google Developers Group. Explorer, adventurer, traveler.

How to create simple and gradient borders in Flutter

4 min read 1329

How To Create Simple And Gradient Border Decorations In Flutter

Organizations today strive to deliver presentable UIs in their mobile apps for end users — a great visual experience brings users back.

When it comes to creating a beautiful UI with ease, Flutter has gained popularity since it’s fundamentally a UI Toolkit that can build beautiful, natively compiled, multi-platform applications from a single codebase.

Visual experiences can be enhanced in many ways, including subtle highlights such as colors, highlights around elements, and border decorations. Borders — in particular around the UI components — can provide intuitiveness as well as hierarchy and structure to the end user.

Borders vary in all shapes and sizes, from normal borders with a single color to ones with a gradient, which require different approaches in Flutter.

In this article, you will learn how to create borders in Flutter. Not just a typical default border, but more interesting ones that make widgets and containers stand out. Aside from the basics, you will also learn how to draw borders with gradients.

Let’s get started.

Understanding the basics with BoxDecoration

BoxDecoration is an immutable class which allows you to paint a box by specifying various properties. When building elements such as a Container, it may be supplied as a decorative property. It is used to give decoration to the Widget where it is utilized, as the name indicates.

Supported decorations include color, gradient, background image, border, and shadow.

Border basics

You can add borders by passing border properties to the BoxDecoration class. There are multiple ways to create an instance of this, such as:

  • Border() constructor
  • Border.all factory pattern
  • Border.merge static method

A specified border on the Container is drawn on top of everything, including; color, gradient, and image.

We made a custom demo for .
No really. Click here to check it out.

Working with simple borders

Let’s begin with drawing a single-colored border around a widget, as demonstrated here:

// dart
Container(
  padding: const EdgeInsets.all(10),
  decoration: BoxDecoration(
    border: Border.all(color: Colors.green[400], width: 3)
  ),
  child: Text("I am inside a border.", style: TextStyle(fontSize: 24),),
);

In this example, the Text widget is wrapped by a container and holds properties such as border width and color using BoxDecoration. Alternatively, it is also possible to provide separate border properties to each side using the Border constructor instead of Border.all(...).

Rounded border

You can change the border radius using the borderRadius property inside BoxDecoration.

// dart
Container(
    width: 100,
    height:100,
    decoration:BoxDecoration(
        color: Colors.blue,                             // Color of the container
        borderRadius: BorderRadius.circular(20.0),      // Radius of the border
        border: Border.all(
            width: 8.0,
            color: Colors.black,                        // Color of the border
        )
    )
)

Border for TextField

An instance of the UnderlineInputBorder class with specific border properties such as color and width is supplied to the InputDecoration class as a parameter, in order to draw the border around TextField.

When the TextFieldis in focus —such as when the user attempts to type text into the field, the border becomes active. The border disappears as soon as the TextFieldloses focus.

// dart
Container(
    child: TextField(
        decoration: InputDecoration(
            enabledBorder: UnderlineInputBorder(
                borderSide: BorderSide(
                    color: Colors.blue,
                    width: 2.0
                ),
            ),
            focusedBorder: InputBorder.none,
            disabledBorder: InputBorder.none,
            hintText: "Enter your name"
        ),
    )
)

Stroke border

Consider a border wrapped around text — it is actually a stroke that encloses every letter itself instead of the outer Container.

// dart
Stack(
  children: [
    Text(
      'I am inside a border.',
      style: TextStyle(
        fontSize: 24,
        foreground: Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = 3
          ..color = Colors.green[400],
      ),
    ),
    Text(
      'I am inside a border.',
      style: TextStyle(
        fontSize: 24,
        color: Colors.blue,
      ),
    ),
  ]
);

If you only use the first Text widget in the stack, it will only add a stroke; you will need to choose a Stack widget that shows the border and fills both of the widgets. Stacking two Text widgets on top of each other that have different font sizes will not work because the text is scaled from the center.

Working with gradient borders

You’ve learned how to implement borders that are relatively basic. Now, let’s implement a gradient border around a Widget, rather than simply a single-toned border.

When decorating a border with a gradient, two properties are required:

  • Gradient, which contains the necessary information such as colors, etc.
  • width of the border stroke

For this, you need to implement an instance of the CustomPainter class as shown in the below code:

// dart
class CustomGradient extends CustomPainter {
  CustomGradient({this.gradient, this.sWidth});

  final Gradient gradient;
  final double sWidth;
  final Paint p = Paint();

  @override
  void paint(Canvas canvas, Size size) {
    Rect innerRect = Rect.fromLTRB(sWidth, sWidth, size.width - sWidth, size.height - sWidth);
    Rect outerRect = Offset.zero & size;

    p.shader = gradient.createShader(outerRect);
    Path borderPath = _calculateBorderPath(outerRect, innerRect);
    canvas.drawPath(borderPath, p);
  }

  Path _calculateBorderPath(Rect outerRect, Rect innerRect) {
    Path outerRectPath = Path()..addRect(outerRect);
    Path innerRectPath = Path()..addRect(innerRect);
    return Path.combine(PathOperation.difference, outerRectPath, innerRectPath);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

Here, instead of drawing the four separate sides of the border, we have added two rectangles as shown in the example, one of which has the same size as the Widget — whereas the other rectangle has the stroke width subtracted from its original size. PathOperation.difference calculates the difference between the two rectangles, and the resulting difference acts as a stroke around the smaller rectangle.

To make the gradient work, we will need to add a shader to the paint object p. In the example above, createShader is the method which accepts the object of outerRect as an argument to make the gradient reach the edges.

Now, to make use of our custom class CustomGradient, we need an enclosing Widget that contains a child widget and draws our gradient around it. Have a look at this example:

class CustomGradientContainer extends StatelessWidget {
  CustomGradientContainer({
    @required gradient,
    @required this.child,
    this.strokeWidth = 3,
  }) : this.painter = CustomGradient(
      gradient: gradient, sWidth: strokeWidth
  );

  final CustomGradient painter;
  final Widget child;
  final VoidCallback onPressed;
  final double strokeWidth;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
        painter: painter, 
        child: child
    );
  }
}

This will draw a gradient around the text. However, this border will be tightly bound around the text itself, which is not conducive to a presentable UI. To improve our UI, we need to insert some further customizations, such as rounded corners and padding.

Take a look at the example below to see how to provide a rounded border to the CustomGradient class:

// dart
class CustomGradient extends CustomPainter {

    ...

    final Gradient gradient;
    final double sWidth;
    final Paint p = Paint();   // Existing declarations

    final double bRadius;      // Declare border radius 

    ...

    @override
    void paint(Canvas canvas, Size size) {

        ...                     // Existing rectangle declarations

        RRect innerRoundedRect = RRect.fromRectAndRadius(innerRect, Radius.circular(bRadius));
        RRect outerRoundedRect = RRect.fromRectAndRadius(outerRect, Radius.circular(bRadius));

        ...                     // Existing PaintObject declarations

        Path borderPath = _calculateBorderPath(outerRoundedRect, innerRoundedRect);

    }

    Path _calculateBorderPath(RRect outerRect, RRect innerRect) {               // Update parameters
        Path outerRectPath = Path()..addRRect(outerRect);
        Path innerRectPath = Path()..addRRect(innerRect);
        return Path.combine(PathOperation.difference, outerRectPath, innerRectPath);
    }

    ...

}

Once the CustomGradient class is updated to specify the border radius property, you can modify CustomGradientContainer to draw the border radius as below:

// dart
class CustomGradientContainer extends StatelessWidget {
  CustomGradientContainer({
    @required gradient,
    @required this.child,
    @required this.onPressed,
    this.strokeWidth = 3,
    this.bRadius = 36,                                                    // Add border radius declaration                      
    this.padding = 12                                                     // Add padding declaration
    }) : this.painter = GradientPainter(
      gradient: gradient, strokeWidth: sWidth, borderRadius: bRadius      // Add border radius declaration
  );

  final GradientPainter painter;
  final Widget child;
  final VoidCallback onPressed;
  final double sWidth;
  final double bRadius;                                                     // Add border radius
  final double padding;                                                     // Add padding

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
        painter: painter,
        child: Container(                                                   // Add container declaration
          padding: EdgeInsets.all(padding),                                 // Add padding declaration
          child: child
        )
    );
  }
}

Here’s what we have done: the constructor of the GradientBorderContainer widget has been modified to declare border the radius and padding. We have then declared the value of the padding to wrap a Container around the child Widget with the respective padding.

Conclusion

Using a CustomPainter class to create a border around a Widget provides a lot more flexibility for decorations in Flutter.

In addition to the basics, we also looked at how to create a changing gradient border around a Widget. You may change the gradient, padding, stroke width, and the width of the outline enclosing the border.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Sneh Pandya Exploring the horizon with a knack for product management. Co-host of the NinjaTalks podcast and community organizer at Google Developers Group. Explorer, adventurer, traveler.

Leave a Reply