Flutter Custom Decoration

Ahmed Tarek
Flutter Community
Published in
6 min readJan 23, 2020

Author:

In this tutorial, you will learn how to create a Flutter custom decoration like the following screenshot:

Custom Decoration

Getting Started

Create a new Flutter project and replace the whole code in the main.dart file by the following code:

import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: 264,
height: 360,
child: Icon(
Icons.wb_sunny,
size: 90,
color: Colors.orange,
),
),
),
),
);
}
}

It’s a very simple app that shows a sun icon at the center of the screen. The sun icon is hosted in a Container, this is the Container you will decorate.

Run the app to see a screenshot like below:

Implement Decoration

To create a custom decoration, you need to implement the Decoration abstract class from the Flutter framework. This abstract class has different implementations in the Flutter framework like ShapeDecoration, BoxDecoration, and FlutterLogoDecoration.

Create a CustomDecoration class by adding the following code:

//1
class CustomDecoration extends Decoration {
//2
CustomDecoration(this._patternLength);

//3
final double _patternLength;

//4
@override
BoxPainter createBoxPainter([onChanged]) {
//5
return _CustomDecorationPainter(_patternLength);
}
}

Here you:

  1. Create a CustomDecoration class that implements the Decoration abstract class.
  2. Create a constructor that accepts a double value which is the length of the pattern you will draw.
  3. Create a class field _patternLength.
  4. Implement the createBoxPainter() method from the Decoration abstract class.
  5. Create an instance of the _CustomDecorationPainter and pass the _patternLength parameter, you will create this class in the next section.

Implement BoxPainter

BoxPainter is a class that paints a particular Decoration.

Create a _CustomDecorationPainter class by implementing BoxPainter abstract class.

//1
class _CustomDecorationPainter extends BoxPainter {
//2
_CustomDecorationPainter(this._patternLength);

//3
final double _patternLength;

//4
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
}
}

Here you:

  1. Create a _CustomDecorationPainter class that implements the BoxPainter abstract class.
  2. Create a constructor that accepts a double value which is the length of the pattern you will draw.
  3. Create a class field _patternLength.
  4. Implement the paint() method from the BoxPainter abstract class, all of your drawing code will be executed by this method, where you have a canvas to draw your decoration, an offset to position all of your drawings to the proper position on the screen and a configuration to know the size of your canvas (the container widget).

Note: Offset is a point , this point has coordinates, a pair of floating numbers that define its exact location on the Canvas.
For example, an Offset with x = 0 and y = 0, represnts the the top left point on the Canvas.

Next, go to the MyApp widget → Container widget and add your CustomDecoration like the following code:

...child: Container(
width: 264,
height: 360,
decoration: CustomDecoration(24.0),
child: Icon(Icons.wb_sunny, size: 90, color: Colors.orange),
),
...

Finally, your app should be able to compile, run it again and you should see no change on your mobile/emulator.

Draw your decoration

To start drawing your decoration, you need to know your widget‘s bounds. To do that, add the following line of code to the paint() method:

final Rect bounds = offset & configuration.size;

Here, you use the “&” operator from the Offset class, it combines an Offset and a Size to create a rectangle whose top-left corner is at the given offset and whose size is given by configuration.size.

Next, call a method called _drawDecoration() from the paint() method, like the following:

@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final Rect bounds = offset & configuration.size;
_drawDecoration(canvas, bounds);
}

Then, create that method by adding the following code to the _CustomDecorationPainter class:

//1
void _drawDecoration(Canvas canvas, Rect bounds) {
//2
Paint innerPaint = Paint()..color = Colors.lightBlue;
Paint outerPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 1.5;

//3
Path innerPath = Path();
Path outerPath = Path();

//4
_addVerticalSides(bounds, innerPath, outerPath);

//5
canvas.drawPath(innerPath, innerPaint);
canvas.drawPath(outerPath, outerPaint);
}

Here you:

  1. Create _drawDecoration() method that accepts two parameters; canvas and rect.
  2. Create two paint objects, the first one to paint the outline rectangle and the second one to paint the solid (filled) rectangle.
  3. Create two path objects, the first one for the outline rectangle and another the second one for the solid (filled) rectangle.
  4. Call the _addVerticalSides() method to add decoration to both of the left and right vertical sides, you will implement this method later.
  5. Draw the innerPath and outerPath to the canvas by using the innerPaint and outerPaint.

Check the following to imagine what is the pattern unit and what are the innerPath and outerPath.

Finally, add the following code to create the _addVerticalSides() method:

//1
void _addVerticalSides(Rect bounds, Path innerPath, Path outerPath){
//2
int patternsCount = bounds.height ~/ _patternLength;
//3
double _accurateLength = bounds.height / patternsCount;

//4
for (var i = 0; i < patternsCount; ++i) {
//5
Rect leftSidePatternRect = Rect.fromLTWH(
bounds.left,
bounds.top + (i * _accurateLength),
_accurateLength,
_accurateLength,
);

//6
Rect rightSidePatternRect = Rect.fromLTWH(
bounds.right - _accurateLength,
bounds.top + (i * _accurateLength),
_accurateLength,
_accurateLength,
);

//7
innerPath.addRotatedRect(leftSidePatternRect);
//8
outerPath.addRect(leftSidePatternRect);

//9
innerPath.addRotatedRect(rightSidePatternRect);
//10
outerPath.addRect(rightSidePatternRect);
}
}

Here you:

  1. Create _addVerticalSides() method that accepts bounds, innerPath, and outerPath.
  2. Calculate how many patterns you need to draw to fill the height of the bounds.
  3. Calculate the accurate length for the pattern to be able to fill the whole height when you multiply it by patternsCount.
  4. Create a for loop from zero to patternsCount to add the number of patterns you need to fill the whole height.
  5. Create a rect with a height and width equal to _accurateLength and on the left side, the rect will be on the top in the first iteration and it will be shifted by (i * _accurateLength) for each iteration.
  6. Create a rect with a height and width equal to _accurateLength and on the right side, the rect will be on the top in the first iteration and it will be shifted by (i * _accurateLength) for each iteration.
  7. Add a rotated rectangle and to the innerPath at the leftSidePatternRect, you will implement the addRotatedRect() extension method later.
  8. Add a rectangle to the outerPath at the leftSidePatternRect.
  9. Add a rotated rectangle and to the innerPath at the rightSidePatternRect, you will implement the addRotatedRect() extension method later.
  10. Add a rectangle to the outerPath at the rightSidePatternRect.

The addRotatedRect() method isn’t a method in the Path class, add the following extension to your main.dart file.

extension on Path {
void addRotatedRect(Rect bounds) {
moveTo(bounds.left, bounds.center.dy);
lineTo(bounds.center.dx, bounds.top);
lineTo(bounds.right, bounds.center.dy);
lineTo(bounds.center.dx, bounds.bottom);
close();
}
}

Run the app to see a screenshot like below:

To draw the top decoration and the bottom decoration, add the following _addHorizontalSides() method. Tt’s pretty similar to the _addVerticalSides() method.

void _addHorizontalSides(Rect bounds, Path innerPath, Path outerPath) {
int patternsCount = bounds.width ~/ _patternLength;

double _accurateLength = (bounds.width / patternsCount);

for (var i = 0; i < patternsCount; ++i) {
Rect topSidePatternRect = Rect.fromLTWH(
bounds.left + (i * _accurateLength),
bounds.top,
_accurateLength,
_accurateLength,
);

Rect bottomSidePatternRect = Rect.fromLTWH(
bounds.left + (i * _accurateLength),
bounds.bottom - _accurateLength,
_accurateLength,
_accurateLength,
);

innerPath.addRotatedRect(topSidePatternRect);
outerPath.addRect(topSidePatternRect);

innerPath.addRotatedRect(bottomSidePatternRect);
outerPath.addRect(bottomSidePatternRect);
}
}

Then call it after calling _addVerticalSides() method.

...
_addVerticalSides(bounds, innerPath, outerPath);
_addHorizontalSides(bounds, innerPath, outerPath);
...

Finally, Run the app to see a screenshot like below:

Awesome, you’re finished 🎉 🙌 👍

Full Source Code:

Run on Dart Pad:

https://dartpad.dev/994551c27a84ce19e44fd8aa83fe14ec

For more tutorials on drawing and animation in Flutter or Android:

Follow me on Twitter https://twitter.com/a_tarek360

and Clap 👏 if you found it useful! Thanks!

--

--

Ahmed Tarek
Flutter Community

I'm Android engineer, in my spare time I make some android animation or play with electronics.