简介
在日常开发中,当Flutter提供的基础组件无法满足开发需求时,特别是产品设计的复杂交互效果,就需要我们自定义组件。 和Android系统一样,自定义组件都包括三种实现方式。
- 原生基础组件的多种组合
- 手动绘制
- 继承系统组件,增加功能效果。
本文主要通过圆形进度条案例来讲解在Flutter中怎么通过手动绘制实现自定义View。文章末尾附有完整源码。
先看效果:
主要参数
CustomPainter
和Android一样,Flutter实现自定义view也需要painter和canvas,即画笔和画布。
- 我们先来看CustomPaint的构造函数。
CustomPaint({
Key key,
this.painter,
this.foregroundPainter,
this.size = Size.zero,
this.isComplex = false,
this.willChange = false,
Widget child,
})
painter:背景画笔。
foregroundPainter:前景画笔。
size:默认代表绘制区域的大小。
isComplex:表示是否复杂绘制。当绘制的效果比较复杂时,可以开启,Flutter内部会对效果做一定的优化。
willChange:和isComplex搭配使用。
child:子view。
Canvas
- Canvas 表示画布,拥有很多的绘制方法。如下表所示:
方法名称 | 功能 |
---|---|
drawLine | 绘制直线 |
drawPoint | 绘制圆点 |
drawPath | 绘制路径 |
drawImage | 绘制图片 |
drawRect | 绘制矩形 |
drawCircle | 绘制圆 |
drawOval | 绘制椭圆 |
drawArc | 绘制弧形 |
Paint
- Paint表示画笔,在Paint中,可以配置画笔的各种属性,如颜色,粗细等。
var paint = Paint()
..strokeWidth = strokeWidth
..strokeCap = strokeCapRound?StrokeCap.round:StrokeCap.butt
..color = Colors.grey
..isAntiAlias = true
..style = PaintingStyle.stroke;
strokeWidth:画笔宽度
strokeCap:画笔结尾的延伸类型,具体看图效果
color:画笔换色
isAntiAlias:是否抗锯齿
style:画笔的样式
功能实现:
绘制灰色圆弧
- 首先定义_GradientCircularProgressIndicator类继承CustomPainter类,并实现paint和shouldRepaint方法。注意自定义view必须继承CustomPainter类。
class _GradientCircularProgressIndicator extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
2 .创建画笔及属性配置
var paint = Paint()
..strokeWidth = strokeWidth //宽度
//画笔结尾形状,这里是圆形
..strokeCap = strokeCapRound?StrokeCap.round:StrokeCap.butt
//颜色
..color = Colors.grey
//抗锯齿
..isAntiAlias = true
//样式
..style = PaintingStyle.stroke;
- 定义外边框矩形大小。
var rect = Offset(_offset,_offset) & Size(size.width-strokeWidth, size.height - strokeWidth);
- 使用canvas绘制圆弧。
canvas.drawArc(rect, _start,totalAngle, false, paint);
绘制彩色进度
- 画完灰色圆弧后,刚进度条增加进度时,绘制彩色进度。首先需要给画笔增加渐变着色器。这里使用的是SweepGradient。
paint.shader = SweepGradient(colors: colors,startAngle: 0.0,endAngle: _value,stops: [0.3,0.6,0.8]).createShader(rect);
6 . 着色器更加进度值变化,然后直接绘制即可。
canvas.drawArc(rect, _start, _value, false, paint);
增加绘制动画
- 当进度条绘制完成后,还要给设置一个动画来完成彩色进度条的动态绘制。这里使用AnimationController动画控制器来实现模拟进度。
_controller = AnimationController(vsync: this,lowerBound: 0,
upperBound:1, duration:
const Duration(seconds: 5));
_controller.forward();//启动动画
- 使用
return Center( child: GradientCircularProgressIndicator(value: _controller.value, radius: 50, strokeWidth: 6, colors: const [Colors.red,Colors.orange,Colors.yellow],strokeCapRound: true,),
);
完整代码
import 'dart:math';
import 'package:flutter/material.dart';
class GradientCircularProgressIndicator extends StatelessWidget {
const GradientCircularProgressIndicator({
super.key,
required this.value,
required this.radius,
required this.colors,
this.strokeCapRound = false,
this.backGroundColor = Colors.grey,
this.totalAngle = 2 * pi,
this.strokeWidth = 2.0,
});
///当前进度 取值范围[0.0->1.0]
final double value;
///圆的半径
final double radius;
///圆环的粗细
final double strokeWidth;
///两端是否为圆角
final bool strokeCapRound;
final Color backGroundColor;
/// 进度条的总弧度 2*PI为整圆
final double totalAngle;
/// 渐变色的数组
final List<Color> colors;
/// 渐变色的终点,对应colors属性
// final List<double> stops;
@override
Widget build(BuildContext context) {
print("radius:$value");
double _offset = .0;
if (strokeCapRound) {
_offset = asin(strokeWidth / (radius * 2 - strokeWidth));
}
var _colors = colors;
_colors ??= [
Theme.of(context).primaryColorLight,
Theme.of(context).cardColor
];
return Transform.rotate(
angle: -pi,
child: CustomPaint(
size: Size.fromRadius(radius),
painter: _GradientCircularProgressIndicator(
value: value,
radius: radius,
strokeWidth: strokeWidth,
strokeCapRound : strokeCapRound,
backGroundColor : backGroundColor,
totalAngle : totalAngle,
colors : _colors,
),
),
);
}
}
class _GradientCircularProgressIndicator extends CustomPainter {
_GradientCircularProgressIndicator(
{required this.value, required this.radius, required this.strokeWidth,
required this.strokeCapRound , required this.backGroundColor,
required this.totalAngle,required this.colors
});
final double value;
final double radius;
final double strokeWidth;
///两端是否为圆角
final bool strokeCapRound;
//背景色
final Color backGroundColor;
/// 进度条的总弧度 2*PI为整圆
final double totalAngle;
/// 渐变色的数组
final List<Color> colors;
@override
void paint(Canvas canvas, Size size) {
if(radius !=null){
size = Size.fromRadius(radius);
}
double _offset = strokeWidth /2.0;
double _value = (value ??.0);
_value = _value.clamp(.0,1.0)*totalAngle;
double _start = 0;
if(strokeCapRound){
_start = asin(strokeWidth/(size.width-strokeWidth));
}
var rect = Offset(_offset,_offset) & Size(size.width-strokeWidth, size.height - strokeWidth);
var paint = Paint()
..strokeWidth = strokeWidth
..strokeCap = strokeCapRound?StrokeCap.round:StrokeCap.butt
..color = Colors.grey
..isAntiAlias = true
if(backGroundColor !=Colors.transparent){
paint.color = backGroundColor;
canvas.drawArc(rect, _start,totalAngle, false, paint);
}
if(_value>0){
paint.shader = SweepGradient(colors: colors,startAngle: 0.0,endAngle: _value,stops: [0.3,0.6,0.8]).createShader(rect);
canvas.drawArc(rect, _start, _value, false, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
总结
Flutter中实现自定义View相对来说比较简单,当然本文介绍的目的是带大家简单入门,喜欢的小伙伴可以收藏学习。切记一定要动手实践。