当前位置: 首页 > news >正文

Flutter 自定义绘制:创建精美的自定义图形

Flutter 自定义绘制:创建精美的自定义图形

掌握 Flutter CustomPainter 的核心概念和实战技巧。

一、自定义绘制的重要性

作为一名追求像素级还原的 UI 匠人,我深知自定义绘制在 Flutter 开发中的重要性。通过 CustomPainter,我们可以创建各种复杂的图形、动画和视觉效果,实现设计稿中的每一个细节。从简单的几何图形到复杂的路径绘制,自定义绘制为我们提供了无限的创意空间。

二、基本概念

1. CustomPainter 类

import 'package:flutter/material.dart'; class MyCustomPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // 绘制逻辑 } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class CustomPainterExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('CustomPainter 示例')), body: Center( child: Container( width: 300, height: 300, child: CustomPaint( painter: MyCustomPainter(), ), ), ), ); } }

2. Canvas 和 Paint

class BasicShapesPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..style = PaintingStyle.fill; // 绘制圆形 canvas.drawCircle( Offset(size.width / 2, size.height / 2), 50, paint, ); // 绘制矩形 paint.color = Colors.red; canvas.drawRect( Rect.fromLTWH(50, 50, 100, 100), paint, ); // 绘制线条 paint.color = Colors.green; paint.style = PaintingStyle.stroke; canvas.drawLine( Offset(0, 0), Offset(size.width, size.height), paint, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }

三、高级绘制技巧

1. 路径绘制

class PathPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.purple ..strokeWidth = 3 ..style = PaintingStyle.stroke; final path = Path(); path.moveTo(50, 50); path.lineTo(250, 50); path.quadraticBezierTo(250, 150, 150, 150); path.lineTo(150, 250); path.close(); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }

2. 渐变和阴影

class GradientPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // 线性渐变 final linearGradient = LinearGradient( colors: [Colors.blue, Colors.purple], begin: Alignment.topLeft, end: Alignment.bottomRight, ); final paint = Paint() ..shader = linearGradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ); // 绘制带阴影的圆形 final shadowPaint = Paint() ..color = Colors.black.withOpacity(0.3) ..maskFilter = MaskFilter.blur(BlurStyle.normal, 10); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 80, shadowPaint, ); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 80, paint, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }

3. 文本绘制

class TextPainterExample extends CustomPainter { @override void paint(Canvas canvas, Size size) { final textSpan = TextSpan( text: 'Flutter CustomPainter', style: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ), ); final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, )..layout( minWidth: 0, maxWidth: size.width, ); textPainter.paint( canvas, Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ), ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }

四、动画绘制

1. 简单动画

class AnimatedPainter extends CustomPainter { final double progress; AnimatedPainter(this.progress); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 4 ..style = PaintingStyle.stroke; final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 3; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, 2 * pi * progress, false, paint, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class AnimatedPainterExample extends StatefulWidget { @override _AnimatedPainterExampleState createState() => _AnimatedPainterExampleState(); } class _AnimatedPainterExampleState extends State<AnimatedPainterExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(); _animation = Tween<double>(begin: 0, end: 1).animate(_controller); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('动画绘制示例')), body: Center( child: Container( width: 300, height: 300, child: AnimatedBuilder( animation: _animation, builder: (context, child) { return CustomPaint( painter: AnimatedPainter(_animation.value), ); }, ), ), ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }

五、实战案例

1. 自定义图表

class ChartPainter extends CustomPainter { final List<double> data; ChartPainter(this.data); @override void paint(Canvas canvas, Size size) { if (data.isEmpty) return; final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..style = PaintingStyle.stroke; final maxValue = data.reduce((a, b) => a > b ? a : b); final minValue = data.reduce((a, b) => a < b ? a : b); final valueRange = maxValue - minValue; final path = Path(); for (int i = 0; i < data.length; i++) { final x = (i / (data.length - 1)) * size.width; final y = size.height - ((data[i] - minValue) / valueRange) * size.height; if (i == 0) { path.moveTo(x, y); } else { path.lineTo(x, y); } } canvas.drawPath(path, paint); // 绘制网格线 final gridPaint = Paint() ..color = Colors.grey.withOpacity(0.3) ..strokeWidth = 1 ..style = PaintingStyle.stroke; // 水平网格线 for (int i = 0; i <= 5; i++) { final y = (i / 5) * size.height; canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint); } // 垂直网格线 for (int i = 0; i <= 5; i++) { final x = (i / 5) * size.width; canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class ChartExample extends StatelessWidget { final List<double> data = [10, 20, 15, 25, 30, 20, 35, 25, 40, 30]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('自定义图表')), body: Center( child: Container( width: 300, height: 300, child: CustomPaint( painter: ChartPainter(data), ), ), ), ); } }

2. 自定义仪表盘

class GaugePainter extends CustomPainter { final double value; // 0-1 GaugePainter(this.value); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 20; // 绘制背景圆弧 final backgroundPaint = Paint() ..color = Colors.grey.withOpacity(0.2) ..strokeWidth = 20 ..style = PaintingStyle.stroke; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi * 1.25, pi * 1.5, false, backgroundPaint, ); // 绘制进度圆弧 final progressPaint = Paint() ..color = Colors.blue ..strokeWidth = 20 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi * 1.25, pi * 1.5 * value, false, progressPaint, ); // 绘制中心文本 final textSpan = TextSpan( text: '${(value * 100).toStringAsFixed(0)}%', style: TextStyle( color: Colors.black, fontSize: 32, fontWeight: FontWeight.bold, ), ); final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, )..layout( minWidth: 0, maxWidth: size.width, ); textPainter.paint( canvas, Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ), ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class GaugeExample extends StatefulWidget { @override _GaugeExampleState createState() => _GaugeExampleState(); } class _GaugeExampleState extends State<GaugeExample> { double _value = 0.5; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('自定义仪表盘')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 300, height: 300, child: CustomPaint( painter: GaugePainter(_value), ), ), SizedBox(height: 20), Slider( value: _value, onChanged: (newValue) { setState(() { _value = newValue; }); }, min: 0, max: 1, ), ], ), ), ); } }

六、性能优化

  1. 合理使用 shouldRepaint:只在需要重绘时返回 true
  2. 缓存绘制结果:对于静态内容,使用 RepaintBoundary
  3. 避免在 paint 方法中创建对象:将 Paint 对象等创建在构造函数中
  4. 使用 saveLayer 谨慎:saveLayer 会创建新的绘制层,可能影响性能
  5. 测试性能:在不同设备上测试绘制性能

七、最佳实践

  1. 模块化:将不同的绘制逻辑分离到不同的 CustomPainter 类中
  2. 参数化:通过构造函数传递参数,使绘制逻辑更加灵活
  3. 组合使用:结合 AnimationController 创建动态效果
  4. 测试:在不同设备和屏幕尺寸上测试绘制效果
  5. 文档:为复杂的绘制逻辑添加注释

CustomPainter 是 Flutter 中实现自定义视觉效果的强大工具,让我们能够创建出独特的 UI 组件。

#flutter #custom-painter #canvas #animation #ui

http://www.jsqmd.com/news/603536/

相关文章:

  • G-Helper实战:华硕笔记本性能与散热智能调控指南
  • 揭秘RAG技术如何给大模型装上“知识外挂”
  • 009.中断实践之实现按键测试|千篇笔记实现嵌入式全栈/裸机篇
  • 保姆级教程:手把手教你用LIOSAM跑通自己的数据集(含常见报错解决)
  • 卡证检测矫正模型C语言接口封装:为嵌入式设备提供轻量级调用
  • Carrada雷达数据集GooGle Colab在线运行
  • 全网爆火的大模型AI知识库,保姆级教程来了
  • win 安装openclaw (docker镜像模式),解决18789访问失败问题
  • 3D Face HRN开源镜像价值:免训练、免标注、免3D扫描设备的平民化方案
  • 肿瘤患者如何预防口腔黏膜炎发生?速舒提供科学护理方案 - 资讯焦点
  • 从FAISS到Milvus:一个AI工程师的向量数据库技术栈演进史与踩坑实录
  • COMSOL仿真揭示石墨烯临界耦合光吸收特性:费米能级调控下的光学性能研究
  • 塞尔达传说存档定制指南:打造个性化游戏体验
  • 2026.4 紫题金了
  • CTC语音唤醒模型与数据结构优化实战
  • 嵌入式C++编译时间缩短82%的实战路径(仅限前500名工程师掌握的增量构建秘钥)
  • 想点奶茶外卖,沪上阿姨鲜果茶值得点吗?美团周末五折福利帮你解锁高性价比答案 - 资讯焦点
  • 告别电脑噪音烦恼:用FanControl 264版实现完美风扇控制
  • EdB Prepare Carefully个性化定制指南:打造你的理想RimWorld开局
  • Steam Deck终极模拟器配置指南:EmuDeck一键安装30+经典游戏机
  • lesson70:jQuery Ajax完全指南:从基础到4.0新特性及现代替代优秀的方案引言:jQuery Ajax的时代价值与演进 - Leone
  • AI将取代80%的测试工作?我持反对意见
  • 3分钟搞定Axure RP中文界面:告别英文困扰,专注原型设计
  • 【2026实测】Syncthing下载安装教程:Syncthing文件同步工具使用全攻略 - xiema
  • 实战指南:基于快马生成集成openclaw的爬虫项目,安装即用
  • 从音频到DDR:一文搞懂PCB设计中“包地”、“类差分”和“真差分”走线到底怎么用
  • 跨平台媒体传输新选择:Go2TV 3分钟入门指南
  • Python实战:海康工业相机主动取流(getoneframetimeout)图像数据解析与OpenCV实时显示优化
  • 2026 ICPC Asia Pacific Championship - E. Parallel Sums
  • [Windows] EchoTrace v3.1.0 W信聊天记录导出、分析与年度报告生成工具