Search code examples
flutterdart

How do I create a folder-shaped container in Flutter?


I want to create a folder-shaped container in Flutter (with curves at the top of the container), something like this:

folder example

And I need the borders to be customizable (container colors, border colors, etc.). Is this possible with native Flutter, or do I need a package?


Solution

  • For anyone who needs something similar, I managed to create a shape similar to the one in the question. This shape can accept parameters like margin, border width, border color, and a list of colors for the background (in case the background is a gradient).

    class FolderShape extends CustomPainter {
      const FolderShape({
        required this.backgroundColor,
        required this.borderColor,
        required this.borderWidth,
        this.margin = 0,
        Key? key,
      });
    
      final List<Color> backgroundColor;
      final Color borderColor;
      final double borderWidth;
      final double margin;
    
      @override
      void paint(Canvas canvas, Size size) {
        final borderPaint = Paint();
        final fillPaint = Paint();
    
        /// The border is centered along the path (Path). This means that half of the border's width will be drawn outside the path, and the other half will be drawn inside
        /// By shifting the path inward by the value of borderWidth / 2, the shape is adjusted so that the border drawn inside the original contour matches exactly the correctly size. This way, the entire border stays inside the area originally intended for the content.
        final double inset = borderWidth / 2;
    
        /// The size of the drawing is adjusted based on the margin. This means that the path is drawn within a reduced area to accommodate the margin.
        final double adjustedWidth = size.width - 2 * margin;
        final double adjustedHeight = size.height - 2 * margin;
    
        final path = Path();
    
        // This is drawn from top-left → top-right → bottom-right → bottom-left → top-left
        path.moveTo(margin + adjustedWidth * 0.24 + inset, margin + inset);
    
        path.lineTo(margin + adjustedWidth * 0.38 - inset, margin + inset);
    
        path.quadraticBezierTo(
          margin + adjustedWidth * 0.45,
          margin + inset,
          margin + adjustedWidth * 0.48,
          margin + adjustedHeight * 0.08 + inset,
        );
    
        path.lineTo(margin + adjustedWidth * 0.85 - inset,
            margin + adjustedHeight * 0.08 + inset);
    
        path.quadraticBezierTo(
          margin + adjustedWidth - inset,
          margin + adjustedHeight * 0.08 + inset,
          margin + adjustedWidth - inset,
          margin + adjustedHeight * 0.23,
        );
    
        path.lineTo(
            margin + adjustedWidth - inset, margin + adjustedHeight * 0.85 - inset);
    
        path.quadraticBezierTo(
          margin + adjustedWidth - inset,
          margin + adjustedHeight - inset,
          margin + adjustedWidth * 0.85 - inset,
          margin + adjustedHeight - inset,
        );
    
        path.lineTo(
            margin + adjustedWidth * 0.15 + inset, margin + adjustedHeight - inset);
    
        path.quadraticBezierTo(
          margin + inset,
          margin + adjustedHeight - inset,
          margin + inset,
          margin + adjustedHeight * 0.85 - inset,
        );
    
        path.lineTo(margin + inset, margin + adjustedHeight * 0.15 + inset);
    
        path.quadraticBezierTo(
          margin + inset,
          margin + inset,
          margin + adjustedWidth * 0.15 + inset,
          margin + inset,
        );
    
        path.close();
    
        borderPaint.color = borderColor;
        borderPaint.style = PaintingStyle.stroke;
        borderPaint.strokeWidth = borderWidth;
    
        // Checks whether buttonColor is a single color or a gradient
        if (backgroundColor.length == 1) {
          fillPaint.color = backgroundColor[0];
        } else {
          fillPaint.shader = LinearGradient(
            colors: backgroundColor,
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          ).createShader(
            Rect.fromLTWH(0, 0, size.width, size.height),
          );
        }
    
        fillPaint.style = PaintingStyle.fill;
    
        canvas.drawPath(path, fillPaint);
        canvas.drawPath(path, borderPaint);
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return true;
      }
    }