Drawing shape - flutter

Viewed 125

I'm trying to create a shape using flutter. shape-1 shape-2

I tried to use some solutions but it didn't work. can anyone help thanks

2 Answers

You can create the shapes using the CustomPaint widget together with BezierCurves. Here you can find out how to use BezierCurves with flutter.

An solution with ClipPaths, Stacks and ImageFiltered widgets.

This design is challenging to implement, but it is definitely doable. The three parts, the red shape, white bottom border and shadow must be clipped with an ClipPath.

  • All of these requires an ClipPath with a CustomClipper, I provide an simplified example, using a cubic curve.
  • Then there is coordinating the position of these three parts inside a Stack widget.

Example

import 'dart:ui';

import 'package:flutter/material.dart';

class CustomHeader extends StatelessWidget {
  final Function() onTap;
  final Color backgroundColor;
  final Color borderColor;
  final Color shadowColor;
  final Color splashColor;
  final double height;
  final Widget? child;

  const CustomHeader({
    Key? key,
    required this.height,
    required this.onTap,
    this.child,
    this.backgroundColor = const Color(0xFFD50000),
    this.splashColor = const Color(0xFFCCFFCC),
    this.borderColor = Colors.white,
    this.shadowColor = Colors.black26,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;

    return Stack(
      children: [
        // The shadow effect
        Padding(
          padding: const EdgeInsets.only(top: 18.0),
          child: ImageFiltered(
            imageFilter: ImageFilter.blur(sigmaX: 2, sigmaY: 2),
            child: ClipPath(
              clipper: CustomHeaderClipper(
                  startHeight: height * 0.75,
                  endHeight: height * 0.18
              ),
              child: Container(
                width: width,
                height: height * 0.76,
                color: shadowColor
              ),
            ),
          ),
        ),

        // The white border
        Padding(
          padding: const EdgeInsets.only(top: 16.0),
          child: ClipPath(
            clipper: CustomHeaderClipper(
                startHeight: height * 0.75,
                endHeight: height * 0.18
            ),
            child: Container(
                width: width,
                height: height * 0.75,
                color: borderColor,
            ),
          ),
        ),

        // The red shape
        ClipPath(
          clipBehavior: Clip.antiAliasWithSaveLayer,
          clipper: CustomHeaderClipper(
              startHeight: height * 0.68,
              endHeight: height * 0.2
          ),
          child: Material(
            color: backgroundColor,
            child: InkWell(
              onTap: onTap,
              splashColor: splashColor,
              child: SizedBox(
                  width: width,
                  height: height * 0.8,
                  child: child,
              ),
            ),
          ),
        ),
      ],
    );
  }
}

class CustomHeaderClipper extends CustomClipper<Path> {
  final double startHeight;
  final double endHeight;

  const CustomHeaderClipper({
    required this.startHeight,
    required this.endHeight
  });

  @override
  Path getClip(Size size) {
    double height = size.height;
    double width = size.width;

    return Path()
      ..moveTo(0, 0)
      ..lineTo(0, startHeight)
      ..cubicTo(width * 0.35, height, width * 0.5, height * 0.4, width, endHeight)
      ..lineTo(width, 0)
      ..lineTo(0, 0)
      ..close();
  }

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

class CustomHeaderBackgroundScreen extends StatelessWidget {
  const CustomHeaderBackgroundScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final height = MediaQuery.of(context).size.height / 2.2;

    return Scaffold(
      backgroundColor: Colors.grey[200],
      body: SafeArea(
        child: CustomHeader(
          height: height,
          onTap: () {},
          child: const Padding(
            padding: EdgeInsets.all(8.0),
            child: Text("Your widget logic", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
          ),
        ),
      ),
    );
  }
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => const MaterialApp(home: CustomHeaderBackgroundScreen());
}

void main() => runApp(const App());

Result

Here is how it is running on Chrome Code in action

Note on Accessibility

Different screen sizes and system preferences on text size might make this design hard to accommodate for, especially for getting the text sized well with this right aligned style, so be aware of this.

Related