Custom UI series columns





Preface – Timeline

Besides drawing charts, Canvas can also be used for other purposes. For example, the effect of timeline timeline can be realized after customized drawing. The usual timeline has the following characteristics:

  • Is a list, and the item of the listhighlyNot fixed
  • One side of the list is marked with axes

There are many ways to realize the time axis in Web, Android and IOS. This article will use the decoration property of Container on Flutter to realize the effect of the time axis by drawing on Canvas.

Container

Container is one of the most commonly used Flutter components. Its decoration allows you to create a variety of borders for the Container.

Container(
  width: 100,
  height: 150,
  decoration: BoxDecoration(
    border: Border(left: BorderSide(color: Colors.blue, width: 2)),
    color: Colors.grey.shade400,
  ),
);
Copy the code

As shown in figure 3, set only the left border for the Container. Because the border height follows the Container, which fits the timeline requirement, we can use the border as the timeline and use the Container to implement the item.


BoxDecoration

The border in BoxDecoration is the border property. Usually we use border or BorderDirectional, a subclass of BoxBorder. BorderDirectional, for example, in addition to a few property Settings, there’s a paint method, which is a method that draws borders

void paint(
  Canvas canvas,
  Rect rect, {
  TextDirection? textDirection,
  BoxShape shape = BoxShape.rectangle,
  BorderRadius? borderRadius,
}) {
  if (isUniform) {
    switch (top.style) {
      case BorderStyle.none:
        return;
      case BorderStyle.solid:
        switch (shape) {
          case BoxShape.circle:
            assert(borderRadius == null.'A borderRadius can only be given for rectangular boxes.');
            BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
            break;
          case BoxShape.rectangle:
            if(borderRadius ! =null) {
              BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
              return;
            }
            BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
            break;
        }
        return; }}// omit more....
}

Copy the code

The BorderDirectional Paint method calls the corresponding draw method to draw different border effects based on the various properties we set. This will eventually be called by the _BoxDecorationPainter object generated in the BorderDirectional createBoxPainter method

class BoxDecoration extends Decoration {
// omit more....

  @override
  BoxPainter createBoxPainter([ VoidCallback? onChanged ]) {
    assert(onChanged ! =null || image == null);
    return _BoxDecorationPainter(this, onChanged); }}class _BoxDecorationPainter extends BoxPainter {
// omit more....
  /// Paint the box decoration into the given location on the given canvas.
  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
   // omit more....
   // This is where the paint on the Border is called_decoration.border? .paint( canvas, rect, shape: _decoration.shape, borderRadius: _decoration.borderRadiusas BorderRadius?,
      textDirection: configuration.textDirection,
    );
  }
}
Copy the code

From this, we can inherit BoxBorder and rewrite its paint method, using the Canvas provided by the paint method to draw the effect we want.


Inheritance override of BoxBorder

Parameter to the paint method in BoxBorder

  • Canvas, canvas
  • Rect: Canvas scope
  • TextDirection: indicates the textDirection

Go straight to code

class TimeLineBoxBorder extends BoxBorder {
  @override
  BorderSide get bottom => BorderSide.none;
  @override
  BorderSide get top => BorderSide.none;
  @override
  ShapeBorder scale(double t) => this;
  @override
  EdgeInsetsGeometry get dimensions => EdgeInsetsGeometry.infinity;
  @override
  bool get isUniform => false;

  @override
  voidpaint(Canvas canvas, Rect rect, {TextDirection? textDirection, BoxShape shape = BoxShape.rectangle, BorderRadius? borderRadius}) { canvas.drawLine( Offset(rect.left, rect.top), Offset(rect.left, rect.bottom), paintLine, ); canvas.drawPoints( PointMode.lines, _points(rect), paintPoint, ); }}Copy the code

Set the decoration property of the Container to BoxDecoration and the border to our custom TimeLineBoxBorder.

Container(
  width: 100,
  height: 100,
  decoration: BoxDecoration(border: TimeLineBoxBorder(), color: Colors.grey.shade400),
);
Copy the code

Final effect:

In this way, we implement the custom border by re-painting the BoxBorder. Apply the Container to the ListView and it will look like the timeline effect.

Write in the last

In addition to overwriting the border, we can also overwrite the createBoxPainter method of BoxDecoration to define a BoxPainter to implement more Container backgrounds and decorative borders.


Attached are some renderings:

Example code github