说起自定义控件,做过iOS和android开发的肯定都不陌生,flutter中也提供了相关的api。flutter中原声提供的各类widget已经可以满足了大部分的业务需求,通过不同的组合,我们也能实现一些较为复杂的需求。今天我们来讲一个需要自己绘制的自定义widget。在Android中,我们是通过自定义view的draw->paint->canvas等一些列的api绘制,flutter中同样也提供了方法。
class TestPainter extends CustomPainter{
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
我们先来自定义个Painter,可以看到这个Painter继承了CustomPainter。我们需要实现两个方法,paint相当于Android 中的ondraw,只不过参数中多了一个size参数。
参数size
我们看到size提供了多个构造方法,最主要的是宽和高两个属性。这个相当于我们在android中自定义view的onmeasure方法。可以看到flutter中的size更加的灵活。
参数canvas
写过android自定义view的同学,看到这个api是不是更加的眼熟了。没错大部分的api都是相同的。
---------------------------------------------------------------------------------------------------------------------------------------------------------------
下面我们来写个demo。先来看看效果图:
我们现在来拆分一下
- 六边形
- 内部网格
- 外部圆形
- 6个角的文字
- 动画
开始撸代码~
-
创建widget
-
创建自定义CustomPainter
-
添加绘制算法
1.创建widget
2.创建自定义CustomPainter并初始化
3.绘制方法
绘制文字的时候,要注意文字的宽度和高度
---------------------------------------------------------------------------------------------------------------------------------------------------------------
完整代码
import 'dart:core';
import 'dart:ui' as ui;
import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
class PageTest2 extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new PageTest2State();
}
}
class PageTest2State extends State with SingleTickerProviderStateMixin {
List<double> _ranks = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
AnimationController _controller;
Animation _curve;
@override
void initState() {
_controller = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this);
_curve = new CurvedAnimation(parent: _controller, curve: Curves.fastLinearToSlowEaseIn);
_curve.addListener(() {
setState(() {});
});
_controller.forward();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
_ranks = generateRanks();
_controller.reset();
_controller.forward();
},
child: Text("按"),
),
body: Container(
constraints: BoxConstraints(
minWidth: double.infinity,
minHeight: double.infinity,
),
child: Center(
child: Container(
constraints: BoxConstraints(
minWidth: 200,
minHeight: 200,
),
child: CustomPaint(
painter: TestPainter(_curve.value, _ranks),
),
),
),
),
);
}
List<double> generateRanks() {
List<double> ranks = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
for (int i = 0; i < ranks.length; i++) {
ranks[i] = Random().nextDouble() * 100;
}
return ranks;
}
}
class TestPainter extends CustomPainter {
double _process;// 动画的进度
Paint _polygonPaint;// 绘制六边形的画笔
Paint _frameStrokePaint;// 绘制六边形网格的画笔
Paint _rankPaint;// 绘制中心等级的画笔
double _centerX;// 自定义TestPainter中心x
double _centerY;// 自定义TestPainter中心y
double _sideLength;// 正六边形边长
double _centerCircleRadius;// 中心园半径
List<double> _ranks;// 等级数据
List<String> _labelString = const ["体力", "精神", "物理", "魔法", "防御", "金钱"];
TestPainter(this._process, this._ranks) {
_polygonPaint = new Paint()
..isAntiAlias = true
..style = PaintingStyle.fill //填充
..color = Color(Colors.blueGrey.value);
_frameStrokePaint = new Paint()
..isAntiAlias = true
..style = PaintingStyle.stroke //描边
..color = Color(Colors.green.value);
_rankPaint = new Paint()
..isAntiAlias = true
..style = PaintingStyle.fill //填充
..color = Color(Colors.red.withAlpha(130).value);
_sideLength = 120;
_centerCircleRadius = 120;
}
@override
void paint(Canvas canvas, Size size) {
_centerX = size.width / 2.0;
_centerY = size.height / 2.0;
_drawPolygon(canvas, _sideLength);
_drawPolygonFrame(canvas, _sideLength, 4);
_drawCircle(canvas);
_drawRank(canvas, _sideLength, _ranks);
_drawText(canvas, _sideLength);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
// 绘制文字
void _drawText(Canvas canvas, double slidLength) {
for (int i = 0; i < _labelString.length; i++) {
String label = _labelString[i] + "+" + (_ranks[i] * _process).toStringAsFixed(1);
TextSpan span = new TextSpan(
style: TextStyle(color: Colors.black),
text: label,
);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
double endX = (_centerX + slidLength * sin(pi / 3 * i));
double endY = (_centerY - slidLength * cos(pi / 3 * i));
if (i == 0) {
endX = endX - tp.width / 2;
endY = endY - tp.height;
} else if (i == 1) {
endX = endX;
endY = endY - tp.height / 2;
} else if (i == 2) {
endX = endX;
endY = endY - tp.height / 2;
} else if (i == 3) {
endX = endX - tp.width / 2;
} else if (i == 4) {
endX = endX - tp.width;
endY = endY - tp.height / 2;
} else {
endX = endX - tp.width;
endY = endY - tp.height / 2;
}
tp.paint(canvas, new Offset(endX, endY));
}
}
// 绘制圆形
void _drawCircle(Canvas canvas){
canvas.drawCircle(Offset(_centerX, _centerY), _centerCircleRadius, _frameStrokePaint);
}
// 绘制中心等级
void _drawRank(Canvas canvas, double slidLength, List<double> ranks) {
Path path = new Path();
double percentLength = slidLength / 100 * _process;
path.moveTo(_centerX, _centerY - ranks[0] * percentLength); //起始坐标
for (int i = 0; i < 6; i++) {
double rankRadius = ranks[i] * percentLength;
double endX = (_centerX + rankRadius * sin(pi / 3 * i));
double endY = (_centerY - rankRadius * cos(pi / 3 * i));
path.lineTo(endX, endY);
}
path.close();
canvas.drawPath(path, _rankPaint);
}
// 绘制中心六边形网格
void _drawPolygonFrame(Canvas canvas, double slidLength, int row) {
for (int k = 1; k <= row; k++) {
double tempSlideLength = slidLength / row * k;
Path path = new Path();
path.moveTo(_centerX, _centerY - tempSlideLength);
for (int i = 0; i < 6; i++) {
double endX = (_centerX + tempSlideLength * sin(pi / 3 * i));
double endY = (_centerY - tempSlideLength * cos(pi / 3 * i));
path.lineTo(endX, endY);
canvas.drawLine(
Offset(_centerX, _centerY), Offset(endX, endY), _frameStrokePaint);
}
path.close();
canvas.drawPath(path, _frameStrokePaint);
}
}
// 绘制六边形底
void _drawPolygon(Canvas canvas, double slidLength) {
Path path = new Path();
path.moveTo(_centerX, _centerY - slidLength);
for (int i = 0; i < 6; i++) {
double endX = (_centerX + slidLength * sin(pi / 3 * i));
double endY = (_centerY - slidLength * cos(pi / 3 * i));
path.lineTo(endX, endY);
}
path.close();
canvas.drawPath(path, _polygonPaint);
}
}