Flutter中如何为ClipPath绘制的半圆添加边框
Flutter票证组件半圆边框实现问题
我是Flutter新手,正在开发票证组件。找到一款票证Widget后,参考Stack Overflow写了代码,用ClipPath成功画出了半圆,但没法给这个半圆加边框。需要添加边框的半圆效果如下:
我的参考代码:
import 'package:flutter/material.dart'; import 'package:vector_math/vector_math.dart' as v_math; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( body: SafeArea( child: MyHomePage(), ), ), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ ClipPath( clipper: DolDurmaClipper(right: 40, holeRadius: 20), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all( Radius.circular(15), ), color: Colors.blueAccent, ), width: 300, height: 95, padding: EdgeInsets.all(15), child: Text('first example'), ), ), SizedBox( height: 20, ), ClipPath( clipper: DolDurmaClipper(right: 100, holeRadius: 40), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all( Radius.circular(15), ), color: Colors.amber, ), width: 200, height: 250, padding: EdgeInsets.all(35), child: Text('second example'), ), ), ]); } } class DolDurmaClipper extends CustomClipper<Path> { DolDurmaClipper({@required this.right, @required this.holeRadius}); final double right; final double holeRadius; @override Path getClip(Size size) { final path = Path() ..moveTo(0, 0) ..lineTo(size.width - right - holeRadius, 0.0) ..arcToPoint( Offset(size.width - right, 0), clockwise: false, radius: Radius.circular(1), ) ..lineTo(size.width, 0.0) ..lineTo(size.width, size.height) ..lineTo(size.width - right, size.height) ..arcToPoint( Offset(size.width - right - holeRadius, size.height), clockwise: false, radius: Radius.circular(1), ); path.lineTo(0.0, size.height); path.close(); return path; } @override bool shouldReclip(DolDurmaClipper oldClipper) => true; }
解决方法
直接给Container加border会被ClipPath裁剪掉,所以得用Stack把裁剪后的容器和绘制边框的CustomPaint叠加,让边框路径和裁剪路径完全匹配。另外原代码里的半圆绘制参数也有问题,一起修复了:
修改后的完整代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( body: SafeArea( child: MyHomePage(), ), ), ); } } class MyHomePage extends StatelessWidget { MyHomePage({super.key}); final clipper1 = DolDurmaClipper(right: 40, holeRadius: 20); final clipper2 = DolDurmaClipper(right: 100, holeRadius: 40); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack( children: [ ClipPath( clipper: clipper1, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all( Radius.circular(15), ), color: Colors.blueAccent, ), width: 300, height: 95, padding: EdgeInsets.all(15), child: const Text('first example'), ), ), CustomPaint( size: const Size(300, 95), painter: BorderPainter( clipper: clipper1, borderColor: Colors.black, borderWidth: 2), ), ], ), const SizedBox( height: 20, ), Stack( children: [ ClipPath( clipper: clipper2, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all( Radius.circular(15), ), color: Colors.amber, ), width: 200, height: 250, padding: EdgeInsets.all(35), child: const Text('second example'), ), ), CustomPaint( size: const Size(200, 250), painter: BorderPainter( clipper: clipper2, borderColor: Colors.black, borderWidth: 2), ), ], ), ], ); } } class DolDurmaClipper extends CustomClipper<Path> { DolDurmaClipper({required this.right, required this.holeRadius}); final double right; final double holeRadius; @override Path getClip(Size size) { final path = Path() ..moveTo(0, 0) ..lineTo(size.width - right - holeRadius, 0.0) ..arcToPoint( Offset(size.width - right, 0), clockwise: false, radius: Radius.circular(holeRadius), // 修复:用holeRadius代替1,正确绘制半圆 ) ..lineTo(size.width, 0.0) ..lineTo(size.width, size.height) ..lineTo(size.width - right, size.height) ..arcToPoint( Offset(size.width - right - holeRadius, size.height), clockwise: false, radius: Radius.circular(holeRadius), // 同样修复这里 ); path.lineTo(0.0, size.height); path.close(); return path; } @override bool shouldReclip(DolDurmaClipper oldClipper) => right != oldClipper.right || holeRadius != oldClipper.holeRadius; } class BorderPainter extends CustomPainter { final DolDurmaClipper clipper; final Color borderColor; final double borderWidth; BorderPainter({ required this.clipper, required this.borderColor, required this.borderWidth, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = borderColor ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; final path = clipper.getClip(size); canvas.drawPath(path, paint); } @override bool shouldRepaint(BorderPainter oldDelegate) => borderColor != oldDelegate.borderColor || borderWidth != oldDelegate.borderWidth || clipper.right != oldDelegate.clipper.right || clipper.holeRadius != oldDelegate.clipper.holeRadius; }
关键改动点
- 修复半圆绘制错误:原代码
arcToPoint里的radius用了Radius.circular(1),改成holeRadius才能画出指定大小的半圆。 - 叠加边框绘制:用
Stack把裁剪后的容器和CustomPaint放在一起,CustomPaint复用裁剪器的路径来画边框,保证形状完全匹配。 - 优化重绘逻辑:修改
shouldReclip和shouldRepaint,只有当参数变化时才重绘,避免不必要的性能消耗。
内容的提问来源于stack exchange,提问作者user20528344
相关产品推荐
相关产品推荐

