Bram  Vanbilsen
Bram Vanbilsen

Reputation: 6505

How to draw a custom rounded rectangle border (ShapeBorder), in Flutter?

I'm trying to extend the ShapeBorder class to add some functionality. But just playing around with the paint method, I found something that I did not expect:

enter image description here The corners of the border and the corners of the rectangle do not seem to match. I used the following code:

class CustomRoundedRectangleBorder extends ShapeBorder {

  final double borderWidth;
  final BorderRadius borderRadius;

  const CustomRoundedRectangleBorder({
    this.borderWidth: 1.0,
    this.borderRadius: BorderRadius.zero,
  })
      : assert(borderRadius != null);

  @override
  EdgeInsetsGeometry get dimensions {
    return new EdgeInsets.all(borderWidth);
  }

  @override
  ShapeBorder scale(double t) {
    return new CustomRoundedRectangleBorder(
      borderWidth: borderWidth * (t),
      borderRadius: borderRadius * (t),
    );
  }

  @override
  ShapeBorder lerpFrom(ShapeBorder a, double t) {
    assert(t != null);
    if (a is CustomRoundedRectangleBorder) {
      return new CustomRoundedRectangleBorder(
        borderWidth: ui.lerpDouble(a.borderWidth, borderWidth, t),
        borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t),
      );
    }
    return super.lerpFrom(a, t);
  }

  @override
  ShapeBorder lerpTo(ShapeBorder b, double t) {
    assert(t != null);
    if (b is CustomRoundedRectangleBorder) {
      return new CustomRoundedRectangleBorder(
        borderWidth: ui.lerpDouble(borderWidth, b.borderWidth, t),
        borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t),
      );
    }
    return super.lerpTo(b, t);
  }

  @override
  Path getInnerPath(Rect rect, { TextDirection textDirection }) {
    return new Path()
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(
          borderWidth));
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection textDirection }) {
    return new Path()
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
  }

  @override
  void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
    rect = rect.deflate(borderWidth / 2.0);

    Paint paint;
    final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
    paint = new Paint()
      ..color = Colors.red
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth;
    canvas.drawRRect(borderRect, paint);
  }
}

And created the rectangle as follows:

new Container(
              height: 100.0,
              width: 200.0,
              padding: new EdgeInsets.all(10.0),
              decoration: new ShapeDecoration(
                color: Colors.black,
                shape: new CustomRoundedRectangleBorder(
                  borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
                  borderWidth: 10.0,
                ),
                // side: new BorderSide(color: Colors.white)
              ),
              child: new Center(child: new Text("My Button"),),
            ),

I feel like the Flutter source code takes a similar approach, but perhaps I'm not seeing something.

EDIT Changing the style of my paint to PaintingStyle.fill thus drawing a rectangle over the original rectangle instead of borders, I do seem to get the correct borders:

  void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {

//    rect = rect.deflate(borderWidth / 2.0);

    Paint paint;
    final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
    paint = new Paint()
      ..color = Colors.red.withOpacity(0.25)
      ..style = PaintingStyle.fill
      ..strokeWidth = borderWidth;
    canvas.drawRRect(borderRect, paint);
  }

I'm still puzzled on how to do this...

Upvotes: 38

Views: 138377

Answers (6)

vivek nakalgaonkar
vivek nakalgaonkar

Reputation: 271

You can use canvas.drawRRect :

canvas.drawRRect(
    RRect.fromRectAndRadius(
        Rect.fromLTWH(
            size.width / 2 - gap - smallMarkWidth - 15,
            gap * 8,
            gap + 70,
            gap * 5,
        ),
        Radius.circular(15.0)
    ),
    backgroundPaint,
);

Upvotes: 27

Abdifatah Mohamed
Abdifatah Mohamed

Reputation: 357

This worked for me!

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration:
      ShapeDecoration(
          shape: RoundedRectangleBorder(
          side:new  BorderSide(color: Color(0xFF2A8068)), 
          borderRadius: new BorderRadius.all(new Radius.circular(4))),
          color: Color(0xFF2A8068)),
      child: Theme(
        data: Theme.of(context).copyWith(
            buttonTheme: ButtonTheme.of(context).copyWith(
                materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)),
        child: OutlineButton(
          shape:  RoundedRectangleBorder(
              side:new  BorderSide(color: Color(0xFF2A8068)), //the outline color
              borderRadius: new BorderRadius.all(new Radius.circular(4))),
          child: Text(
            "ابدأ", //your text here
             style: new TextStyle(
              color: Colors.white, //your textColor
            ),
          ),
          onPressed: () => {},
        ),
      ),
    );

Upvotes: 16

lekanbaruwa
lekanbaruwa

Reputation: 177

I published a simple package based off @Bram Vanbilsen's code, which gives you control on how you want to draw the shape and it's border whichever way you want.

The package can be found on pub.dev here: https://pub.dev/packages/custom_rounded_rectangle_border

Upvotes: 0

Yash
Yash

Reputation: 5968

You can use ClipRRect widget instead of drawRect and it is simple to use.

ClipRRect(
        borderRadius: BorderRadius.circular(10),
        child: Container(),
      ),

Upvotes: 5

Nikhat Shaikh
Nikhat Shaikh

Reputation: 3685

Draw custom rounded border with shadow.

new Container(

                decoration:
                new BoxDecoration(
                  borderRadius: new BorderRadius.circular(10.0),
                  color: Colors.white,

                  boxShadow: [
                    new BoxShadow(
                        color: Colors.grey,
                        blurRadius: 3.0,
                        offset: new Offset(1.0, 1.0))
                  ],
                ),
)

Draw custom rounded border without shadow.

new Container(
        decoration:
                new BoxDecoration(
                  borderRadius: new BorderRadius.circular(10.0),
                  color: Colors.grey,


                ),
)

Upvotes: 7

Thomas
Thomas

Reputation: 9257

You should use canvas.drawPath not drawRect

Paint paint = new Paint()
  ..color = borderColor
  ..style = PaintingStyle.stroke
  ..strokeWidth = borderWidth;

canvas.drawPath(getOuterPath(rect), paint);

also if you just want a border, its enough to use

 @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return new Path()
      ..fillType = PathFillType.evenOdd
      ..addPath(getOuterPath(rect), Offset.zero);
  }

Upvotes: 15

Related Questions