需求
页面设计需要一个可以切换和不切换的按钮,右下角有一个小三角形作为能否切换的标识,增强按钮的交互性和可识别性。由于指示小三角为等腰三角且带有圆角的不规则图形(如下图所示),搜索没有发现存现成组件,所以思考如何制作一个。
实现方式
经请教Mentor得知Canvas自定义画布很强大,可以实现自定义的任意图形,所以先去学习了下基础的Flutter Canvas绘画。首先最简单的的Canvas画布逻辑正如其名,我们需要一个画布和画笔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Paint _paint = Paint() ..color = Colors.white ..style = PaintingStyle.fill ..strokeWidth = 2;
class MyCustomPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { var path = Path() ..moveTo(50, 36) ..lineTo(50, 26) ..lineTo(40, 36) ..close(); canvas.clipPath(path); Rect rect = Rect.fromPoints(const Offset(1, 1), const Offset(49, 35)); RRect rRect = RRect.fromRectAndRadius(rect, const Radius.circular(3.0)); canvas.drawRRect(rRect, _paint); }
@override bool shouldRepaint(CustomPainter oldDelegate) => false; }
|
首先画布是2D的,那么便可以用二维坐标系来定位画笔进行绘画任意图形。Flutter官方规定画布的二维坐标系原点位于左上角,右横向为X轴正方向,纵下方向为Y轴正方向。
圆角三角形指示器的实现思路为,直接使用 dart ui 的 RRect class ,直接绘制出圆角矩形,然后裁出所需要的三角,实现思路图示如下
优化
完成后发现会出现canvas画布会因按钮动画造成多次重绘消耗性能,因此为组件包裹上RepaintBoundary,阻隔按钮动画与canvas以减少重绘进行优化。
还可以做的优化便是分离长宽大小计算。
最后放上源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| class _SwitchButton extends StatelessWidget { const _SwitchButton({required this.text, this.switchable = true, this.btnStyle, this.onPressed});
final String text;
final bool switchable; final ButtonStyle? btnStyle; final VoidCallback? onPressed;
@override Widget build(BuildContext context) { return SizedBox( width: 200, height: 72, child: switchable ? RepaintBoundary( child: CustomPaint( size: const Size(200, 72), foregroundPainter: MyCustomPainter(),
child: RepaintBoundary( child: ElevatedButton( onPressed: onPressed, style: btnStyle ?? ElevatedButton.styleFrom( padding: const EdgeInsets.all(0), backgroundColor: Theme.of(context).primaryColor), child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 25)), )))) : ElevatedButton( onPressed: onPressed, style: btnStyle ?? ElevatedButton.styleFrom( padding: const EdgeInsets.all(0), backgroundColor: Theme.of(context).primaryColor), child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 15)))); } }
Paint _paint = Paint() ..color = Colors.white ..style = PaintingStyle.fill ..strokeWidth = 2;
class MyCustomPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { var path = Path() ..moveTo(200, 72) ..lineTo(200, 52) ..lineTo(180, 72) ..close(); canvas.clipPath(path); Rect rect = Rect.fromPoints(const Offset(1, 1), const Offset(198, 70)); RRect rRect = RRect.fromRectAndRadius(rect, const Radius.circular(3.0)); canvas.drawRRect(rRect, _paint); }
@override bool shouldRepaint(CustomPainter oldDelegate) => false; }
|