前言
最近这段时刻,在看Flutter制作的相关常识。主要是看张风捷特烈的制作小册。经过对小册的学习了解Flutter制作的相关常识,见识到了绚丽多姿的Flutter制作世界。
此文是对制作小册内容的一个实践,经过一个自定义控件的案例,初步探索怎么进行Flutter自定义控件的开发。
话不多说,让咱们开端今天的制作之旅吧。
一,初始版别自定义控件效果图:
我把上述的控件命名为同心边框图形,顾名思义,该控件是由多个半径不同的圆形边框组成的自定义控件,且这些边框具有相同的中心点。
二,控件需求拆解:
经过拆解剖析,该控件的功用能够分为以下几点:
1,不同圆形的半径是等差递加的,即两两相邻的圆形距离是持平的。
2,支撑事务侧传入参数,来确认图形的色彩。
3,底部的两个Slider用来操控自定义控件的中心点。
4,支撑每秒切换圆形边框的色彩。
2.1 同心圆的制作
咱们先完结中心多个同心圆的制作, 很明显关于自定义控件咱们要自定义CustomPainter,然后拿到Canvas进行制作。关于同心圆来说,需求三个参数,即最小圆半径,最大圆半径和色彩数组。其中色彩数组还用来确认同心圆的数量
class ConcentricPainter extends CustomPainter {
ConcentricPainter({this.minRadius = 10, this.maxRadius = 20,
this.colorsList = const [Colors.amber, Colors.cyan]});
final double minRadius;
final double maxRadius;
List<Color> colorsList = [Colors.amber, Colors.cyan];
final Paint mPaint = Paint()
..strokeWidth = 2
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
debugPrint("size.width and size.height=${size.width } ${size.height }");
//将画布移到控件中心
canvas.translate(size.width / 2, size.height / 2);
Path path = Path();
mPaint.color = colorsList[0];
drawCircle(path, canvas, minRadius);
if (colorsList.length > 2) {
//被切割的同心圆份数
int count = colorsList.length - 1;
double divider = (maxRadius - minRadius) / count;
//从1开端,由于0是minRadius
for (int i = 1; i < count; i++) {
path.reset();
mPaint.color = colorsList[i];
drawCircle(path, canvas, minRadius + i * divider);
}
}
path.reset();
mPaint.color = colorsList[colorsList.length - 1];
drawCircle(path, canvas, maxRadius);
}
void drawCircle(Path path, Canvas canvas, double radius) {
path.addOval(Rect.fromCenter(center: Offset.zero, width: radius, height: radius));
canvas.drawPath(path, mPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
2.2 根据Slider来操控同心圆的中心点
调整同心圆的中心点,所以ConcentricPainter需求两个偏移量参数 centerFactorX和centerFactorY
由于需求Slider来操控中心点,所以需求创立一个State
class ConcentricPagerState extends State<ConcentricPager>{
//省掉不重要代码
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, zone) {
debugPrint("zone maxWidth and zone maxHeight ${zone.maxWidth} ${zone.maxHeight}");
return Column(
children: [buildPager(), buildSliderX(), buildSliderY()],
);
});
}
Widget buildSliderX() {
return Padding(
padding: const EdgeInsets.all(30),
child: Slider(
max: 180,
min: -180,
divisions: 360,
value: _valueX,
onChanged: (v) {
setState(() {
_valueX = v;
});
}));
}
Widget buildSliderY() {
return Padding(
padding: const EdgeInsets.all(10),
child: Slider(
max: 180,
min: -180,
divisions: 360,
value: _valueY,
onChanged: (v) {
setState(() {
_valueY = v;
});
}));
}
Widget buildPager() {
return Container(
width: 300,
height: 200,
color: Colors.white,
alignment: Alignment.center,
child: LayoutBuilder(builder: (_, zone) {
return CustomPaint(
size: Size(zone.maxWidth, zone.maxHeight),
// 此刻centerFactorX的值在-1到1之间
painter: ConcentricPainter(manage: manager,
minRadius: widget.minRadius, maxRadius: widget.maxRadius,
centerFactorX: _valueX / 180, centerFactorY: _valueY / 180),
);
}),
);
}
}
class ConcentricPainter extends CustomPainter {
ConcentricPainter({this.minRadius = 10, this.maxRadius = 20,
this.centerFactorX = 0, this.centerFactorY = 0, required this.manage})
: super(repaint: manage);
//省掉不重要代码
void drawCircle(Path path, Canvas canvas, double radius, Size size) {
path.addOval(Rect.fromCenter
(center: Offset.zero + Offset(size.width / 2 * centerFactorX, size.height / 2 * centerFactorY),
width: radius, height: radius));
canvas.drawPath(path, mPaint);
}
}
到这里,一个能够调整中心点位置的同心圆控件就完结了
2.3 添加动画,动态改动边框色彩
前置常识,CustomPainter有一个类型为Listenable?的变量_repaint,Listenable是一个抽象类,一般其完成类是ChangeNotifier。当_repaint调用notifyListeners()时,CustomPainter就会重绘。
能够使用Ticker类对象能够完成16ms回调功用
class ConcentricManager with ChangeNotifier {
late List<Color> colorsList;
//原始色彩数组
late List<Color> originalColorList;
late DateTime datetime; // 用来上次更新时刻
ConcentricManager(this.colorsList){
//对colorList数组进行深复制
originalColorList = List<Color>.generate(
colorsList.length,//要传入的长度,不能大于_categoryListModel.goods的长度,可根据实际需求设置
(int index){//创立新的QualitySamplingGoodsModel,默认体系会主动帮咱们创立
return colorsList[index];
},growable: true);
datetime = DateTime.now();
}
//每秒会回调一次
void tick(DateTime now) {
randomColorList();
notifyListeners();
}
void randomColorList(){
int count = 0;
int size = colorsList.length;
Random random = Random();
List<Color> tempList = [];
//随机色彩改变
colorsList.forEach((element) {
int index = random.nextInt(size-1);
debugPrint("index:${index}");
tempList.add(originalColorList[index]);
count++;
});
colorsList = tempList;
}
}
class ConcentricPagerState extends State<ConcentricPager> with SingleTickerProviderStateMixin {
double _valueX = 20.0;
double _valueY = 20.0;
late Ticker _ticker;
late ConcentricManager manager;
@override
void initState(){
super.initState();
manager = ConcentricManager(widget.colorsList);
_ticker = createTicker(_tick)
..start();
}
//省掉无关代码
void _tick(Duration elapsed) {
DateTime now = DateTime.now();
//1s更新一次
if(now.millisecondsSinceEpoch - manager.datetime.millisecondsSinceEpoch > 1000){
manager..datetime = now..tick(now);
}
}
至此初始版别的自定义控件功用便完结了
三,功用扩展
现在咱们对此控件进行功用扩展,让它支撑同心方形边框的制作。 很明显方形同心边框和圆形同心边框只有制作细节不同,其他都相同。所以咱们能够把ConcentricPainter抽成抽象类,提供抽象方法让其子类完成
abstract class ConcentricPainter extends CustomPainter {
final ConcentricManager manage;
ConcentricPainter(
{this.minRadius = 10,
this.maxRadius = 20,
this.centerFactorX = 0,
this.centerFactorY = 0,
required this.manage})
: super(repaint: manage);
final double minRadius;
final double maxRadius;
final double centerFactorX;
final double centerFactorY;
List<Color> colorsList = [Colors.amber, Colors.cyan];
final Paint mPaint = Paint()
..strokeWidth = 2
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
debugPrint("size.width and size.height=${size.width} ${size.height}");
canvas.translate(size.width / 2, size.height / 2);
Path path = Path();
colorsList = manage.colorsList;
mPaint.color = colorsList[0];
drawShape(path, canvas, minRadius, size);
if (colorsList.length > 2) {
//被切割的同心圆份数
int count = colorsList.length - 1;
double divider = (maxRadius - minRadius) / count;
//从1开端,由于0是minRadius
for (int i = 1; i < count; i++) {
path.reset();
mPaint.color = colorsList[i];
drawShape(path, canvas, minRadius + i * divider, size);
}
}
path.reset();
mPaint.color = colorsList[colorsList.length - 1];
drawShape(path, canvas, maxRadius, size);
}
void drawShape(Path path, Canvas canvas, double radius, Size size);
}
class ConcentricCirclePainter extends ConcentricPainter {
ConcentricCirclePainter(
{minRadius = 10,
maxRadius = 20,
centerFactorX = 0,
centerFactorY = 0,
required manage})
: super(
minRadius: minRadius,
maxRadius: maxRadius,
centerFactorX: centerFactorX,
centerFactorY: centerFactorY,
manage: manage);
@override
void drawShape(Path path, Canvas canvas, double radius, Size size) {
path.addOval(Rect.fromCenter(
center: Offset.zero +
Offset(size.width / 2 * centerFactorX,
size.height / 2 * centerFactorY),
width: radius,
height: radius));
canvas.drawPath(path, mPaint);
}
}
class ConcentricSquarePainter extends ConcentricPainter {
ConcentricSquarePainter(
{minRadius = 10,
maxRadius = 20,
centerFactorX = 0,
centerFactorY = 0,
required manage})
: super(
minRadius: minRadius,
maxRadius: maxRadius,
centerFactorX: centerFactorX,
centerFactorY: centerFactorY,
manage: manage);
@override
void drawShape(Path path, Canvas canvas, double radius, Size size) {
path.addRect(Rect.fromCenter(
center: Offset.zero +
Offset(size.width / 2 * centerFactorX,
size.height / 2 * centerFactorY),
width: radius,
height: radius));
canvas.drawPath(path, mPaint);
}
}
至此这个自定义控件的开发介绍就告一段落了。我们还能够在这个基础上开发更多功用,例如给控件添加淡入淡出的动画,或许扩展出更多不同图形的同心控件。学会了制作技巧,就能把自己的想法经过制作方法展现出来。希望我们能从此文中获益,然后敞开制作之旅,体会到制作的趣味。
本文Demo请点击
感谢
张风捷特烈的小册–Flutter 制作指南 – 妙笔生花
该小册详细记载了Flutter制作的相关常识,激发了我的制作兴趣,然后有了本文的诞生。
您若喜欢,请点赞、重视,您的鼓舞是我行进的动力