Search code examples
androidflutteruser-interface

Border at corner only in flutter


I trying to make border at corner only in container.

Container(
  padding: const EdgeInsets.only(right: 8.0, left: 8.0),
  height: 60,
  child: Card(
    elevation: 2,
    shape: RoundedRectangleBorder(
        side: BorderSide(color: appThemeColor.shade200, width: 0.5),
        borderRadius: BorderRadius.circular(5)),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.only(
                topRight: Radius.circular(15),
                bottomRight: Radius.circular(15)),
            color: Colors.deepPurple,
          ),
          width: 5,
        ),
      ],
    ),
  ),
)

enter image description here


Solution

  • This can be done with CustomPaint widget with CustomPainter set as foregroundPainter:

        import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'CustomPaint spotlight',
          home: Scaffold(
            appBar: AppBar(
              title: Text('CustomPaint spotlight'),
            ),
            body: Center(
              child: CustomPaint(
                foregroundPainter: BorderPainter(),
                child: Container(
                  width: 200,
                  height: 100,
                  color: Colors.deepOrange[50],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class BorderPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        double sh = size.height; // for convenient shortage
        double sw = size.width; // for convenient shortage
        double cornerSide = sh * 0.1; // desirable value for corners side
    
        Paint paint = Paint()
          ..color = Colors.black
          ..strokeWidth = 1.5
          ..style = PaintingStyle.stroke
          ..strokeCap = StrokeCap.round;
    
        Path path = Path()
          ..moveTo(cornerSide, 0)
          ..quadraticBezierTo(0, 0, 0, cornerSide)
          ..moveTo(0, sh - cornerSide)
          ..quadraticBezierTo(0, sh, cornerSide, sh)
          ..moveTo(sw - cornerSide, sh)
          ..quadraticBezierTo(sw, sh, sw, sh - cornerSide)
          ..moveTo(sw, cornerSide)
          ..quadraticBezierTo(sw, 0, sw - cornerSide, 0);
    
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(BorderPainter oldDelegate) => false;
    
      @override
      bool shouldRebuildSemantics(BorderPainter oldDelegate) => false;
    }
    

    This produces the result like:

    You need to know that CustomPaint does not crop its child corners and in some cases that might be discouraging. You have two ways of solving this:

    • Easy one - to cast rounded borders on its child and make them be visibly equal to CustomPaint borders.

    • Robust one - to wrap child with ClipPath with a path that copies CustomPaint path

        import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'CustomPaint spotlight',
          home: Scaffold(
            appBar: AppBar(
              title: Text('CustomPaint spotlight'),
            ),
            body: Center(
              child: CustomPaint(
                foregroundPainter: BorderPainter(),
                child: ClipPath(
                  clipper: BorderClipper(),
                  child: Container(
                    width: 200,
                    height: 100,
                    color: Colors.deepOrange[50],
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class BorderPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        double sh = size.height; // for convenient shortage
        double sw = size.width; // for convenient shortage
        double cornerSide = sh * 0.1; // desirable value for corners side
    
        Paint paint = Paint()
          ..color = Colors.black
          ..strokeWidth = 1.5
          ..style = PaintingStyle.stroke
          ..strokeCap = StrokeCap.round;
    
        Path path = Path()
          ..moveTo(cornerSide, 0)
          ..quadraticBezierTo(0, 0, 0, cornerSide)
          ..moveTo(0, sh - cornerSide)
          ..quadraticBezierTo(0, sh, cornerSide, sh)
          ..moveTo(sw - cornerSide, sh)
          ..quadraticBezierTo(sw, sh, sw, sh - cornerSide)
          ..moveTo(sw, cornerSide)
          ..quadraticBezierTo(sw, 0, sw - cornerSide, 0);
    
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(BorderPainter oldDelegate) => false;
    
      @override
      bool shouldRebuildSemantics(BorderPainter oldDelegate) => false;
    }
    
    class BorderClipper extends CustomClipper<Path> {
      @override
      Path getClip(Size size) {
        double sh = size.height; // for convenient shortage
        double sw = size.width; // for convenient shortage
        double cornerSide = sh * 0.1;
    
        Path path = Path()
          ..moveTo(cornerSide, 0)
          ..quadraticBezierTo(0, 0, 0, cornerSide)
          ..moveTo(0, sh - cornerSide)
          ..quadraticBezierTo(0, sh, cornerSide, sh)
          ..moveTo(sw - cornerSide, sh)
          ..quadraticBezierTo(sw, sh, sw, sh - cornerSide)
          ..moveTo(sw, cornerSide)
          ..quadraticBezierTo(sw, 0, sw - cornerSide, 0);
        return path;
      }
    
      @override
      bool shouldReclip(CustomClipper<Path> oldClipper) => false;
    }