自定义组件介绍
- 何时需要自定义组件
- Flutter提供的现有组件无法满足业务需求,或者需要封装一些通用组件,多处复用时
- Flutter中自定义组件有三种方式:
- 通过组合其它组件
- 自绘组件
- 实现RenderObject
自定义组件类型
- 组合其它Widget
- 该方式通过拼装其它组件来组合成一个新的组件。
- 自绘组件
- 若遇到无法通过现有的组件来实现需要的UI时,可以通过自绘组件的方式来实现,例如一个颜色渐变的圆形进度条。
- 实现RenderObject
- Flutter提供的自身具有UI外观的组件,如
Text
、Image
都是通过相应的RenderObject
渲染出来的。
- Flutter提供的自身具有UI外观的组件,如
自绘组件
- 对于一些复杂或不规则的UI,可能无法通过组合其它组件的方式来实现
- 比如需要一个渐变的圆形进度条、一个棋盘等。
- 几乎所有的UI系统都提供一个自绘UI的接口,该接口通常会提供一块2D画布
Canvas
Canvas
内部封装一些基本绘制的API,开发者可以通过Canvas
绘制各种自定义图形。- 在Flutter中,提供了一个
CustomPaint
组件,可结合画笔CustomPainter
来实现自定义图形绘制。
CustomPaint类
CustomPaint
构造函数:
CustomPaint({Key key,this.painter, this.foregroundPainter,this.size = Size.zero, this.isComplex = false, this.willChange = false, Widget child, //子节点,可以为空})
- 阐述一下参数含义
painter
: 背景画笔,会显示在子节点后面;foregroundPainter
: 前景画笔,会显示在子节点前面size
:当child为null时,代表默认绘制区域大小,如果有child则忽略此参数,画布尺寸则为child尺寸。如果有child但是想指定画布为特定大小,可以使用SizeBox包裹CustomPaint实现。isComplex
:是否复杂的绘制,如果是,Flutter会应用一些缓存策略来减少重复渲染的开销。willChange
:和isComplex
配合使用,当启用缓存时,该属性代表在下一帧中绘制是否会改变。
示例:五子棋盘
- 下面是五子棋游戏中棋盘和棋子的绘制来演示自绘UI的过程:
import 'package:flutter/material.dart';import 'dart:math';class CustomPaintRoute extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: CustomPaint(size: Size(300, 300), //指定画布大小painter: MyPainter(),),);}}class MyPainter extends CustomPainter {@overridevoid paint(Canvas canvas, Size size) {double eWidth = size.width / 15;double eHeight = size.height / 15;//画棋盘背景var paint = Paint()..isAntiAlias = true..style = PaintingStyle.fill //填充..color = Color(0x77cdb175); //背景为纸黄色canvas.drawRect(Offset.zero & size, paint);//画棋盘网格paint..style = PaintingStyle.stroke //线..color = Colors.black87..strokeWidth = 1.0;for (int i = 0; i <= 15; ++i) {double dy = eHeight * i;canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);}for (int i = 0; i <= 15; ++i) {double dx = eWidth * i;canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);}//画一个黑子paint..style = PaintingStyle.fill..color = Colors.black;canvas.drawCircle(Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2),min(eWidth / 2, eHeight / 2) - 2,paint,);//画一个白子paint.color = Colors.white;canvas.drawCircle(Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2),min(eWidth / 2, eHeight / 2) - 2,paint,);}//在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true@overridebool shouldRepaint(CustomPainter oldDelegate) => true;}
绘制性能问题
- 在实现自绘控件时应该考虑到性能开销,下面是关于性能优化的建议:
- 利用好
shouldRepaint
返回值;在UI树重新build时,控件在绘制前都会先调用该方法以确定是否有必要重绘;若绘制的UI不依赖外部状态,就应该始终返回false
,因为外部状态改变导致重新build时不会影响UI外观; - 若绘制依赖外部状态,则应该在
shouldRepaint
中判断依赖的状态是否改变,如果已改变则应返回true
来重绘,反之则应返回false
不需要重绘。 - 绘制尽可能多的分层;在上面五子棋示例中,将棋盘和棋子的绘制放在了一起,这样会有一个问题:由于棋盘始终是不变的,用户每次落子时变的只是棋子,但是如果按照上面的代码来实现,每次绘制棋子时都要重新绘制一次棋盘,是没必要的。优化的方法就是将棋盘单独抽为一个组件,并设置其
shouldRepaint
回调值为false
,将棋盘组件作为背景。将棋子的绘制放到另一个组件中,这样每次落子时只需要绘制棋子。
- 利用好
- 自绘控件理论上可以实现任何2D图形外观
- Flutter提供的所有组件最终都是通过调用Canvas绘制出来的,只不过绘制的逻辑被封装起来。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题