起初我想向您描述获得所需形状的方法等等......
但被这个有趣的编程挑战冲昏了头脑,最终创建了一个实际的小部件:)
它依赖于 font_awesome_flutter 包,所以不要忘记安装它(用于心形图标)。 font_awesome_flutter
所以小部件的源代码是:
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;
}
}
它是动态的,按钮是内置的。您可以使用 2 个选项 - onDislike() 和 onLike() 回调。
使用不同尺寸的示例。
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: () {},
),
),
],
),
),
),
);
}
}
有一个 _gapSize 参数,它是两个圆之间的间隙。您需要的已经在里面了(默认为 2%),但是您可以通过更改它来获得一些很酷的其他变体。例如,以下是总宽度 20% 的间隙: