简体   繁体   English

使用 CustomPaint 自定义形状

[英]Flutter custom shape using CustomPaint

I just want my created shape so that i can stack a widget to achieve the Image below.我只想要我创建的形状,以便我可以堆叠一个小部件来实现下面的图像。 i am trying to get the transparent shape at the back ground of the X and Love.我试图在 X 和 Love 的背景上获得透明的形状。 I Try using the shape maker but my mouse designing is not perfect.我尝试使用形状生成器,但我的鼠标设计并不完美。 here is the code generated from the shape maker这是形状生成器生成的代码

    child: CustomPaint(
      size: Size(400,(400*0.2857142857142857).toDouble()),
      painter: RPSCustomPainter(),
    ),

class RPSCustomPainter extends CustomPainter{
  
  @override
  void paint(Canvas canvas, Size size) {
    
    

  Paint paint_0 = new Paint()
      ..color = Color.fromARGB(255, 33, 150, 243)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;
     
         
        Path path_0 = Path();
        path_0.moveTo(size.width*0.2137714,size.height*0.2524000);
        path_0.cubicTo(size.width*0.1736143,size.height*0.4775500,size.width*0.1973000,size.height*0.6711500,size.width*0.2153286,size.height*0.7510000);
        path_0.cubicTo(size.width*0.2270429,size.height*0.7777500,size.width*0.2705286,size.height*0.9439500,size.width*0.3556000,size.height*0.7521500);
        path_0.cubicTo(size.width*0.3856000,size.height*0.6504000,size.width*0.3970143,size.height*0.6162000,size.width*0.4283571,size.height*0.7526000);
        path_0.cubicTo(size.width*0.4669286,size.height*0.8264000,size.width*0.5172429,size.height*0.9022500,size.width*0.5719714,size.height*0.7500000);
        path_0.cubicTo(size.width*0.6146429,size.height*0.5440500,size.width*0.5914429,size.height*0.3101000,size.width*0.5713714,size.height*0.2514000);
        path_0.cubicTo(size.width*0.5520714,size.height*0.1778000,size.width*0.4875429,size.height*0.0767500,size.width*0.4296571,size.height*0.2527000);
        path_0.cubicTo(size.width*0.4023714,size.height*0.3646000,size.width*0.3816857,size.height*0.3850000,size.width*0.3557143,size.height*0.2523000);
        path_0.cubicTo(size.width*0.3438571,size.height*0.2086000,size.width*0.2652143,size.height*0.0579000,size.width*0.2137714,size.height*0.2524000);
        path_0.close();
    
        canvas.drawPath(path_0, paint_0);
      
        
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return true;
      }
      
    }

what i am trying to achieve我正在努力实现的目标

在此处输入图片说明

my result.我的结果。 the shape is not perfect.形状并不完美。

thanks谢谢

结果图像

At first I wanted to describe you the ways you can achieve the shape you want and so on...起初,我想向您描述实现您想要的形状的方法等等......

But got carried away with this fun programming challenge and ended up creating an actual widget :)但是被这个有趣的编程挑战冲昏了头脑,最终创建了一个真正的小部件:)

It depends on the font_awesome_flutter package, so don't forget to install it (for the heart icon).它取决于font_awesome_flutter包,所以不要忘记安装它(对于心形图标)。 font_awesome_flutter font_awesome_flutter

So the widget's source code is:所以小部件的源代码是:

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

extension ToRadians on int {
  double get toRadians => this * (math.pi / 180.0);
}

enum _ButtonType { like, dislike }

class LikeOrNot extends StatelessWidget {
  final VoidCallback onLike;
  final VoidCallback onDislike;

  // Percents from total widget width, default - 2%
  final _gapSizeRatio = 0.02;

  final _likeIconColor = const Color(0xffb85076);
  final _dislikeIconColor = Colors.white;

  const LikeOrNot({
    Key? key,
    required this.onLike,
    required this.onDislike,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 2,
      child: LayoutBuilder(
        builder: (context, constraints) {
          final buttonPaddings = constraints.maxHeight * 0.1;
          final halfWidth = constraints.maxWidth / 2;
          return Stack(
            children: [
              Positioned.fill(
                child: CustomPaint(
                  painter: RPSCustomPainter(
                    gapSizeRatio: _gapSizeRatio,
                  ),
                ),
              ),
              Positioned(
                left: 0,
                bottom: 0,
                top: 0,
                right: halfWidth + constraints.maxWidth * _gapSizeRatio,
                child: SizedBox.expand(
                  child: Padding(
                    padding: EdgeInsets.all(buttonPaddings),
                    child: _buildButton(_ButtonType.dislike),
                  ),
                ),
              ),
              Positioned(
                right: 0,
                bottom: 0,
                top: 0,
                left: halfWidth + constraints.maxWidth * _gapSizeRatio,
                child: SizedBox.expand(
                  child: Padding(
                    padding: EdgeInsets.all(buttonPaddings),
                    child: _buildButton(_ButtonType.like),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  Widget _buildButton(_ButtonType buttonType) {
    final isPositiveAction = buttonType == _ButtonType.like;

    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        shape: const CircleBorder(),
        primary: isPositiveAction ? _dislikeIconColor : _likeIconColor,
        onPrimary: isPositiveAction ? _likeIconColor : _dislikeIconColor,
        padding: EdgeInsets.zero,
        elevation: 10,
        shadowColor: Colors.black54,
      ),
      onPressed: onDislike,
      child: FractionallySizedBox(
        widthFactor: 0.35,
        heightFactor: 0.35,
        child: FittedBox(
          child: isPositiveAction
              ? const FaIcon(FontAwesomeIcons.heart)
              : const Icon(Icons.close),
        ),
      ),
    );
  }
}

class RPSCustomPainter extends CustomPainter {
  final double gapSizeRatio;

  RPSCustomPainter({
    required this.gapSizeRatio,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black.withOpacity(0.08)
      ..style = PaintingStyle.fill
      ..strokeWidth = 1;

    final path = Path();

    final gapSize = size.width * gapSizeRatio;
    final arcRadius = size.height / 2 - gapSize / 2;

    final leftCircleCenter = Offset(
      size.width * 0.25 - gapSize / 2,
      size.height / 2,
    );
    final rightCircleCenter = Offset(
      size.width * 0.75 + gapSize / 2,
      size.height / 2,
    );

    path.arcTo(
      Rect.fromCircle(
        center: leftCircleCenter,
        radius: arcRadius,
      ),
      45.toRadians,
      270.toRadians,
      false,
    );

    final bezierOffset = arcRadius * (105 / 360);

    path.quadraticBezierTo(
      size.width / 2,
      size.height * 0.30,
      rightCircleCenter.dx - arcRadius + bezierOffset,
      rightCircleCenter.dy - arcRadius + bezierOffset,
    );

    path.arcTo(
      Rect.fromCircle(
        center: rightCircleCenter,
        radius: arcRadius,
      ),
      225.toRadians,
      270.toRadians,
      false,
    );

    path.quadraticBezierTo(
      size.width / 2,
      size.height * 0.70,
      leftCircleCenter.dx + arcRadius - bezierOffset,
      leftCircleCenter.dy + arcRadius - bezierOffset,
    );

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

It's dynamic, the buttons are built-in.它是动态的,按钮是内置的。 You get 2 options to work with - onDislike() and onLike() callbacks.您有 2 个选项可以使用 - onDislike()onLike()回调。
An example using different sizes.使用不同尺寸的示例。

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

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          colors: [Color(0xffc0496f), Color(0xffdb6b59)],
          begin: Alignment.centerLeft,
          end: Alignment.centerRight,
        ),
      ),
      child: Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          title: const Text('Test'),
          backgroundColor: Colors.transparent,
          elevation: 0,
        ),
        body: Container(
          width: double.infinity,
          padding: const EdgeInsets.all(20.0),
          child: Column(
            children: [
              for (final size in List.generate(5, (index) => index++))
                FractionallySizedBox(
                  widthFactor: 1.0 - size * 0.2,
                  child: LikeOrNot(
                    onLike: () {},
                    onDislike: () {},
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

示例图像

There's a _gapSize parameter which is the gap between two circles.有一个_gapSize参数,它是两个圆之间的间隙。 The one you need is already inside (2% default) but you can get some cool other variations by changing it.你需要的已经在里面(默认为 2%),但你可以通过改变它来获得一些很酷的其他变化。 For example, here's a gap of 20% total width:例如,这里有一个总宽度为 20% 的间隙:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM