'Flutter: How to draw a star

I am trying to custom my container shape to look like this:

enter image description here

I tried to do it with customPaint but I don't know very well this widget so I need help.

How can I draw a shape like this? Is customPaint the right solution?



Solution 1:[1]

You can copy paste run full code below
modified code of package https://pub.dev/packages/flutter_custom_clippers 's
StarClipper https://github.com/lohanidamodar/flutter_custom_clippers/blob/master/lib/src/star_clipper.dart

code snippet

class StarClipper extends CustomClipper<Path>
 @override
   Path getClip(Size size) {
     ...
     double radius = halfWidth / 1.3;
...
Container(
      height: 200,
      width: 200,
      child: ClipPath(
        clipper: StarClipper(14),
        child: Container(
          height: 150,
          color: Colors.green[500],
          child: Center(child: Text("+6", style: TextStyle(fontSize: 50),)),
        ),
      ),
    ),

working demo

enter image description here

full code

import 'package:flutter/material.dart';
import 'dart:math' as math;

class StarClipper extends CustomClipper<Path> {
  StarClipper(this.numberOfPoints);

  /// The number of points of the star
  final int numberOfPoints;

  @override
  Path getClip(Size size) {
    double width = size.width;
    print(width);
    double halfWidth = width / 2;

    double bigRadius = halfWidth;

    double radius = halfWidth / 1.3;

    double degreesPerStep = _degToRad(360 / numberOfPoints);

    double halfDegreesPerStep = degreesPerStep / 2;

    var path = Path();

    double max = 2 * math.pi;

    path.moveTo(width, halfWidth);

    for (double step = 0; step < max; step += degreesPerStep) {
      path.lineTo(halfWidth + bigRadius * math.cos(step),
          halfWidth + bigRadius * math.sin(step));
      path.lineTo(halfWidth + radius * math.cos(step + halfDegreesPerStep),
          halfWidth + radius * math.sin(step + halfDegreesPerStep));
    }

    path.close();
    return path;
  }

  num _degToRad(num deg) => deg * (math.pi / 180.0);

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    StarClipper oldie = oldClipper as StarClipper;
    return numberOfPoints != oldie.numberOfPoints;
  }
}


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              height: 200,
              width: 200,
              child: ClipPath(
                clipper: StarClipper(14),
                child: Container(
                  height: 150,
                  color: Colors.green[500],
                  child: Center(child: Text("+6", style: TextStyle(fontSize: 50),)),
                ),
              ),
            ),

          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Solution 2:[2]

I was trying to create Turkish flag for fun. Creating crescent was easy but creating star is not look easy. so I just tried to use my geometric and trigonometric knowledge to draw but no success. while continuing searching I encountered this link . Yeess I found what I look for. of course it was about different topic but functions was useful. so I reach my goal at the end of night.

So creating custom stars are possible with CustomPainter since now. Because I m sleepy and tired of being awake whole night I share all of code block with no further explanation. You can adjust offsetts according to your need. if any question. I m ready to answer. Enjoy coding.

 class TurkishFlagPaint extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        // x and y coordinates of starting point 
    
        final bx = 95;
        final by = 0;
    
        final paint = Paint();
        paint.color = Colors.white;
        final innerCirclePoints = 5; //how many edges you need?
        final innerRadius = 80 / innerCirclePoints;
        final innerOuterRadiusRatio = 2.5;
        final outerRadius = innerRadius * innerOuterRadiusRatio;
    
        List<Map> points =
            calcStarPoints(bx, by, innerCirclePoints, innerRadius, outerRadius);
        var star = Path()..moveTo(points[0]['x'], points[0]['y']);
        points.forEach((point) {
          star.lineTo(point['x'], point['y']);
        });
    
        canvas.drawPath(
          Path.combine(
            PathOperation.union,
    
            //this combine for crescent
            Path.combine(
              PathOperation.difference,
              Path()..addOval(Rect.fromCircle(center: Offset(-20, 0), radius: 80)),
              Path()
                ..addOval(Rect.fromCircle(center: Offset(2, 0), radius: 60))
                ..close(),
            ),
            star,// I also combine cresscent with star
          ),
          paint,
        );
      }


      //This function is life saver. 
//it produces points for star edges inner and outer. if you need to //rotation of star edges. 
// just play with - 0.3  value in currX and currY equations.
    
      List<Map> calcStarPoints(
          centerX, centerY, innerCirclePoints, innerRadius, outerRadius) {
        final angle = ((math.pi) / innerCirclePoints);
        var angleOffsetToCenterStar = 0;
    
        var totalPoints = innerCirclePoints * 2; // 10 in a 5-points star
        List<Map> points = [];
        for (int i = 0; i < totalPoints; i++) {
          bool isEvenIndex = i % 2 == 0;
          var r = isEvenIndex ? outerRadius : innerRadius;
    
          var currY =
              centerY + math.cos(i * angle + angleOffsetToCenterStar - 0.3) * r;
          var currX =
              centerX + math.sin(i * angle + angleOffsetToCenterStar - 0.3) * r;
          points.add({'x': currX, 'y': currY});
        }
        return points;
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => true;
    }

now you can use painter in a widget;

Center(
   child: CustomPaint(
       painter: TurkishFlagPaint(),
     ),
   ),

and the result will be like this : enter image description here

Solution 3:[3]

import 'package:flutter/material.dart';
import 'dart:math' as math;

class StarClipper extends CustomClipper<Path> {
  StarClipper(this.numberOfPoints);

  /// The number of points in the star
  final int numberOfPoints;

  @override
  Path getClip(Size size) {
    double width = size.width;
    print(width);
    double halfWidth = width / 2;

    double bigRadius = halfWidth;

    double radius = halfWidth / 1.3;

    double degreesPerStep = _degToRad(360 / numberOfPoints);

    double halfDegreesPerStep = degreesPerStep / 2;

    var path = Path();

    double max = 2 * math.pi;

    path.moveTo(width, halfWidth);

    for (double step = 0; step < max; step += degreesPerStep) {
      path.lineTo(halfWidth + bigRadius * math.cos(step),
          halfWidth + bigRadius * math.sin(step));
      path.lineTo(halfWidth + radius * math.cos(step + halfDegreesPerStep),
          halfWidth + radius * math.sin(step + halfDegreesPerStep));
    }

    path.close();
    return path;
  }

  num _degToRad(num deg) => deg * (math.pi / 180.0);

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    StarClipper oldie = oldClipper as StarClipper;
    return numberOfPoints != oldie.numberOfPoints;
  }
}


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              height: 200,
              width: 200,
              child: ClipPath(
                clipper: StarClipper(14),
                child: Container(
                  height: 150,
                  color: Colors.green[500],
                  child: Center(child: Text("+6", style: TextStyle(fontSize: 50),)),
                ),
              ),
            ),

          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Solution 4:[4]

Yes, CustomPaint is the right solution. You can calculate the Path (series of points around the Container) and then paint it with the drawPath method of the canvas.

For example, the path of a triangle would look like this:

return Path()
      ..moveTo(0, y)
      ..lineTo(x / 2, 0)
      ..lineTo(x, y)
      ..lineTo(0, y);

The Path starts at (0,y) (top-left), then a line to (x/2,0) (bottom-center) gets added and so on. This snipped was taken from this answer.

Solution 5:[5]

This code will help you build a centered star, to use it you just have to instantiate it in a ClipPath

import 'package:flutter/material.dart';
import 'dart:math' as math;

const STAR_POINTS = 5;

class StarClipper extends CustomClipper<Path> {
   @override
   Path getClip(Size size) {
   var centerX = size.width / 2;
   var centerY = size.height / 2;

   var path = Path();

   var radius = size.width / 2;
   var inner = radius / 2;
   var rotation = math.pi / 2 * 3;
   var step = math.pi / STAR_POINTS;

   path.lineTo(centerX, centerY - radius);

   for (var i = 0; i < STAR_POINTS; i++) {
    var x = centerX + math.cos(rotation) * radius;
    var y = centerY + math.sin(rotation) * radius;
    path.lineTo(x, y);
    rotation += step;

    x = centerX + math.cos(rotation) * inner;
    y = centerY + math.sin(rotation) * inner;
    path.lineTo(x, y);
    rotation += step;
  }

  path.lineTo(centerX, centerY - radius);
  path.close();

  return path;
 }

 @override
 bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

///  Instance
ClipPath(
 clipper: StarClipper(),
 child: Container(
    color: Colors.red,
    width: 80,
    height: 80,
    ),
)

image-test

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 chunhunghan
Solution 2 Bilal ÅžimÅŸek
Solution 3 Taha Gorme
Solution 4 Herry
Solution 5 ArmandoHackCode