flutterdartflutter-custompainter

Custom Shape in Flutter with Custom Painter


I want to make a shape as seen in the photo below with container in Flutter.

enter image description here

How can that shape be made?


Solution

  • import 'dart:ui' as ui show lerpDouble;
    
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            // This is the theme of your application.
            //
            // Try running your application with "flutter run". You'll see the
            // application has a blue toolbar. Then, without quitting the app, try
            // changing the primarySwatch below to Colors.green and then invoke
            // "hot reload" (press "r" in the console where you ran "flutter run",
            // or simply save your changes to "hot reload" in a Flutter IDE).
            // Notice that the counter didn't reset back to zero; the application
            // is not restarted.
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      // This widget is the home page of your application. It is stateful, meaning
      // that it has a State object (defined below) that contains fields that affect
      // how it looks.
    
      // This class is the configuration for the state. It holds the values (in this
      // case the title) provided by the parent (in this case the App widget) and
      // used by the build method of the State. Fields in a Widget subclass are
      // always marked "final".
    
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          // This call to setState tells the Flutter framework that something has
          // changed in this State, which causes it to rerun the build method below
          // so that the display can reflect the updated values. If we changed
          // _counter without calling setState(), then the build method would not be
          // called again, and so nothing would appear to happen.
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        // This method is rerun every time setState is called, for instance as done
        // by the _incrementCounter method above.
        //
        // The Flutter framework has been optimized to make rerunning build methods
        // fast, so that you can just rebuild anything that needs updating rather
        // than having to individually change instances of widgets.
        return Scaffold(
          appBar: AppBar(
            // Here we take the value from the MyHomePage object that was created by
            // the App.build method, and use it to set our appbar title.
            title: Text(widget.title),
          ),
          body: Center(
            // Center is a layout widget. It takes a single child and positions it
            // in the middle of the parent.
            child: Column(
              // Column is also a layout widget. It takes a list of children and
              // arranges them vertically. By default, it sizes itself to fit its
              // children horizontally, and tries to be as tall as its parent.
              //
              // Invoke "debug painting" (press "p" in the console, choose the
              // "Toggle Debug Paint" action from the Flutter Inspector in Android
              // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
              // to see the wireframe for each widget.
              //
              // Column has various properties to control how it sizes itself and
              // how it positions its children. Here we use mainAxisAlignment to
              // center the children vertically; the main axis here is the vertical
              // axis because Columns are vertical (the cross axis would be
              // horizontal).
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Card(
                  shape: RoundedCustomRectangleBorder(
                      borderRadius: BorderRadius.circular(10),
                      side: const BorderSide(color: Colors.red)),
                  child: const SizedBox(
                    width: 100,
                    height: 100,
                  ),
                )
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    
    class RoundedCustomRectangleBorder extends OutlinedBorder {
      /// Creates a rounded rectangle border.
      ///
      /// The arguments must not be null.
      const RoundedCustomRectangleBorder({
        BorderSide side = BorderSide.none,
        this.borderRadius = BorderRadius.zero,
      }) : super(side: side);
    
      /// The radii for each corner.
      final BorderRadiusGeometry borderRadius;
    
      @override
      EdgeInsetsGeometry get dimensions {
        return EdgeInsets.all(side.width);
      }
    
      @override
      ShapeBorder scale(double t) {
        return RoundedRectangleBorder(
          side: side.scale(t),
          borderRadius: borderRadius * t,
        );
      }
    
      @override
      ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
        if (a is RoundedRectangleBorder) {
          return RoundedRectangleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
          );
        }
        if (a is CircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius: borderRadius,
            circleness: 1.0 - t,
          );
        }
        return super.lerpFrom(a, t);
      }
    
      @override
      ShapeBorder? lerpTo(ShapeBorder? b, double t) {
        if (b is RoundedRectangleBorder) {
          return RoundedRectangleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
          );
        }
        if (b is CircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius: borderRadius,
            circleness: t,
          );
        }
        return super.lerpTo(b, t);
      }
    
      /// Returns a copy of this RoundedRectangleBorder with the given fields
      /// replaced with the new values.
      @override
      RoundedRectangleBorder copyWith(
          {BorderSide? side, BorderRadiusGeometry? borderRadius}) {
        return RoundedRectangleBorder(
          side: side ?? this.side,
          borderRadius: borderRadius ?? this.borderRadius,
        );
      }
    
      @override
      Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
        return Path()
          ..addRRect(borderRadius
              .resolve(textDirection)
              .toRRect(rect)
              .deflate(side.width));
      }
    
      @override
      Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
        return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
      }
    
      @override
      void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
        switch (side.style) {
          case BorderStyle.none:
            break;
          case BorderStyle.solid:
            final double width = side.width;
            if (width == 0.0) {
              canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect),
                  side.toPaint());
            } else {
              final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
              final RRect inner = outer.deflate(width);
              final Paint paint = Paint()..color = side.color;
              final Paint paint2 = Paint()..color = Colors.white;
    
              canvas.drawDRRect(outer, inner, paint);
              canvas.drawPath(
                  Path()..addPath(getTrianglePath(40, 40), const Offset(-30, -19)),
                  paint);
              canvas.drawPath(
                  Path()
                    ..addPath(gettickpath(const Size(35, 35)), const Offset(79, 0)),
                  paint2);
            }
        }
      }
    
      Path getTrianglePath(double x, double y) {
        return Path()
          ..moveTo(x * 2.240499, y * 0.4641165)
          ..lineTo(x * 3.039413, y * 0.4641165)
          ..arcToPoint(Offset(x * 3.240499, y * 0.6652069),
              radius: Radius.elliptical(x * 0.2010859, y * 0.2010904),
              rotation: 0,
              largeArc: false,
              clockwise: true)
          ..lineTo(x * 3.240499, y * 1.464117)
          ..close();
      }
    
      Path gettickpath(Size size) {
        Path path_0 = Path();
        path_0.moveTo(size.width * 0.1513658, size.height * 0.5394244);
        path_0.lineTo(size.width * 0.006621293, size.height * 0.3682648);
        path_0.arcToPoint(Offset(size.width * 0.006621293, size.height * 0.3311515),
            radius: Radius.elliptical(
                size.width * 0.02537649, size.height * 0.02780121),
            rotation: 0,
            largeArc: false,
            clockwise: true);
        path_0.lineTo(size.width * 0.03812633, size.height * 0.2940383);
        path_0.arcToPoint(Offset(size.width * 0.06963136, size.height * 0.2940383),
            radius: Radius.elliptical(
                size.width * 0.02115734, size.height * 0.02317892),
            rotation: 0,
            largeArc: false,
            clockwise: true);
        path_0.lineTo(size.width * 0.1671953, size.height * 0.4093256);
        path_0.lineTo(size.width * 0.3761818, size.height * 0.1623874);
        path_0.arcToPoint(Offset(size.width * 0.4076869, size.height * 0.1623874),
            radius: Radius.elliptical(
                size.width * 0.02115734, size.height * 0.02317892),
            rotation: 0,
            largeArc: false,
            clockwise: true);
        path_0.lineTo(size.width * 0.4391919, size.height * 0.1995007);
        path_0.arcToPoint(Offset(size.width * 0.4391919, size.height * 0.2366139),
            radius: Radius.elliptical(
                size.width * 0.02537649, size.height * 0.02780121),
            rotation: 0,
            largeArc: false,
            clockwise: true);
        path_0.lineTo(size.width * 0.1828709, size.height * 0.5394244);
        path_0.arcToPoint(Offset(size.width * 0.1513658, size.height * 0.5394244),
            radius: Radius.elliptical(
                size.width * 0.02115734, size.height * 0.02317892),
            rotation: 0,
            largeArc: false,
            clockwise: true);
        path_0.close();
        return path_0;
      }
    
      @override
      bool operator ==(Object other) {
        if (other.runtimeType != runtimeType) {
          return false;
        }
        return other is RoundedRectangleBorder &&
            other.side == side &&
            other.borderRadius == borderRadius;
      }
    
      @override
      int get hashCode => Object.hash(side, borderRadius);
    
      @override
      String toString() {
        return '${objectRuntimeType(this, 'RoundedRectangleBorder')}($side, $borderRadius)';
      }
    }
    
    class _RoundedRectangleCustomToCircleBorder extends OutlinedBorder {
      const _RoundedRectangleCustomToCircleBorder({
        BorderSide side = BorderSide.none,
        this.borderRadius = BorderRadius.zero,
        required this.circleness,
      }) : super(side: side);
    
      final BorderRadiusGeometry borderRadius;
    
      final double circleness;
    
      @override
      EdgeInsetsGeometry get dimensions {
        return EdgeInsets.all(side.width);
      }
    
      @override
      ShapeBorder scale(double t) {
        return _RoundedRectangleCustomToCircleBorder(
          side: side.scale(t),
          borderRadius: borderRadius * t,
          circleness: t,
        );
      }
    
      @override
      ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
        if (a is RoundedRectangleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
            circleness: circleness * t,
          );
        }
        if (a is CircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius: borderRadius,
            circleness: circleness + (1.0 - circleness) * (1.0 - t),
          );
        }
        if (a is _RoundedRectangleCustomToCircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
            circleness: ui.lerpDouble(a.circleness, circleness, t)!,
          );
        }
        return super.lerpFrom(a, t);
      }
    
      @override
      ShapeBorder? lerpTo(ShapeBorder? b, double t) {
        if (b is RoundedRectangleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
            circleness: circleness * (1.0 - t),
          );
        }
        if (b is CircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius: borderRadius,
            circleness: circleness + (1.0 - circleness) * t,
          );
        }
        if (b is _RoundedRectangleCustomToCircleBorder) {
          return _RoundedRectangleCustomToCircleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius:
                BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
            circleness: ui.lerpDouble(circleness, b.circleness, t)!,
          );
        }
        return super.lerpTo(b, t);
      }
    
      Rect _adjustRect(Rect rect) {
        if (circleness == 0.0 || rect.width == rect.height) {
          return rect;
        }
        if (rect.width < rect.height) {
          final double delta = circleness * (rect.height - rect.width) / 2.0;
          return Rect.fromLTRB(
            rect.left,
            rect.top + delta,
            rect.right,
            rect.bottom - delta,
          );
        } else {
          final double delta = circleness * (rect.width - rect.height) / 2.0;
          return Rect.fromLTRB(
            rect.left + delta,
            rect.top,
            rect.right - delta,
            rect.bottom,
          );
        }
      }
    
      BorderRadius? _adjustBorderRadius(Rect rect, TextDirection? textDirection) {
        final BorderRadius resolvedRadius = borderRadius.resolve(textDirection);
        if (circleness == 0.0) {
          return resolvedRadius;
        }
        return BorderRadius.lerp(resolvedRadius,
            BorderRadius.circular(rect.shortestSide / 2.0), circleness);
      }
    
      @override
      Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
        return Path()
          ..addRRect(_adjustBorderRadius(rect, textDirection)!
              .toRRect(_adjustRect(rect))
              .deflate(side.width));
      }
    
      @override
      Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
        return Path()
          ..addRRect(
              _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)));
      }
    
      @override
      _RoundedRectangleCustomToCircleBorder copyWith(
          {BorderSide? side,
          BorderRadiusGeometry? borderRadius,
          double? circleness}) {
        return _RoundedRectangleCustomToCircleBorder(
          side: side ?? this.side,
          borderRadius: borderRadius ?? this.borderRadius,
          circleness: circleness ?? this.circleness,
        );
      }
    
      @override
      void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
        switch (side.style) {
          case BorderStyle.none:
            break;
          case BorderStyle.solid:
            final double width = side.width;
            if (width == 0.0) {
              canvas.drawRRect(
                  _adjustBorderRadius(rect, textDirection)!
                      .toRRect(_adjustRect(rect)),
                  side.toPaint());
            } else {
              final RRect outer = _adjustBorderRadius(rect, textDirection)!
                  .toRRect(_adjustRect(rect));
              final RRect inner = outer.deflate(width);
              final Paint paint = Paint()..color = side.color;
              canvas.drawDRRect(outer, inner, paint);
            }
        }
      }
    
      @override
      bool operator ==(Object other) {
        if (other.runtimeType != runtimeType) {
          return false;
        }
        return other is _RoundedRectangleCustomToCircleBorder &&
            other.side == side &&
            other.borderRadius == borderRadius &&
            other.circleness == circleness;
      }
    
      @override
      int get hashCode => Object.hash(side, borderRadius, circleness);
    
      @override
      String toString() {
        return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
      }
    }
    

    Result enter image description here