Search code examples
flutterdartuser-interface

Custom Curve in Flutter


I am developing an App using flutter and came across a custom curve like structure and I have no Idea how to create one. I am sharing a screenshot for the same.

enter image description here

I did some research and found that it might be possible with clip path widget but again I have no clue how will that work. I would also appreciate if someone also explained how to solve this.


Solution

  • A clean way to do this is using a ShapeBorder, so you can use it on any container. Similar to the ClipPath widget, you can define a path to use. The ShapeBorder gets you the path from a bounding Rect of this Container. In the code below, I draw the top arc above this rect, so the top border aligns (and you can put other Widgets right above this container too).

    import 'package:flutter/material.dart';
    
    class WavyBorder extends ShapeBorder {
      const WavyBorder({this.radius = 32});
    
      final double radius;
    
      @override
      EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
    
      @override
      Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
        throw UnimplementedError();
      }
    
      @override
      Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
        return Path()
          // The top curve is above this container's Rect, keep this in mind with your other Widgets
          ..moveTo(rect.topLeft.dx, rect.topLeft.dy - radius)
          // The 90 degree arc at the top
          ..relativeArcToPoint(
            Offset(radius, radius),
            radius: Radius.circular(radius),
            clockwise: false,
          )
          // The horizontal line
          ..lineTo(rect.topRight.dx - radius, rect.topRight.dy)
          // The 90 degree arc at the bottom
          ..relativeArcToPoint(
            Offset(radius, radius),
            radius: Radius.circular(radius),
          )
          // Then just visit the bottom corners normally
          ..lineTo(rect.bottomRight.dx, rect.bottomRight.dy)
          ..lineTo(rect.bottomLeft.dx, rect.bottomLeft.dy)
          ..close();
      }
    
      @override
      void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
    
      @override
      ShapeBorder scale(double t) {
        throw UnimplementedError();
      }
    }
    

    Then, wherever you want to use it, you can add it in the ShapeDecoration of your Container:

    Container(
      decoration: ShapeDecoration(
        shape: WavyBorder(radius: 32),
        ...
      ),
      child: ...,
    ),
    

    Which looks like this:

    enter image description here

    Complete code for usage (main.dart):

    import 'package:flutter/material.dart';
    import 'wavy_container.dart';
    
    void main() {
      runApp(const MaterialApp(
        home: HomeScreen(),
        debugShowCheckedModeBanner: false,
      ));
    }
    
    class HomeScreen extends StatefulWidget {
      const HomeScreen({super.key});
    
      @override
      State<HomeScreen> createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
      final TextEditingController controller = TextEditingController();
      final FocusNode focusNode = FocusNode();
    
      @override
      void initState() {
        super.initState();
        controller.text = 'Hallo';
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Flex(
            direction: Axis.vertical,
            mainAxisSize: MainAxisSize.max,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Expanded(
                child: Text(
                  'This text fits neatly above the container. Watch out with the left margin right above it though!',
                  style: Theme.of(context).textTheme.headlineMedium,
                  textAlign: TextAlign.center,
                ),
              ),
              Expanded(
                flex: 2,
                child: Container(
                  padding: const EdgeInsets.all(8),
                  decoration: ShapeDecoration(
                    shape: const WavyBorder(radius: 32),
                    color: Theme.of(context).colorScheme.primary,
                  ),
                  child: Text(
                    'Look at that pretty wavy border above here!',
                    style: Theme.of(context)
                        .textTheme
                        .headlineLarge
                        ?.copyWith(color: Colors.white),
                    textAlign: TextAlign.center,
                  ),
                ),
              )
            ],
          ),
        );
      }
    }