国庆节马上就要到了,为了庆祝伟大祖国的生日,现在朋友圈的各位大佬们也都更新了社交的头像,渐变色的红旗相当有格调,今天小菜通过 Flutter 方式自定义一个简单的渐变国旗头像;
ACEStarPage
1. 国旗范围及辅助线
首先绘制国旗是一件非常严肃的事情,小菜特意在维基百科上查询了官方国旗的介绍,也算是增加了一些知识点;
- 先将旗面划分为 4 个等分长方形,再将左上方长方形划分长宽 15×10 个方格;
- 大五角星的中心位于该长方形上 5 下 5、左 5 右 10 之处。大五角星外接圆的直径为 6 单位长度;
- 四颗小五角星的中心点,第一颗位于上 **2 下 8、左 10 右 5,第二颗位于上 4 下 6、左 12 右 3,第三颗位于上 7 下 3、左 12 右 3,第四颗位于上 9 下 1、左 10 右 5 之处;
- 每颗小五角星外接圆的直径均为 2 单位长度。四颗小五角星均有一角尖正对大五角星的中心点;
- 中央政府网站上的国旗用颜色分别是 #DE2910 和 #FFDE00;
头像一般是正方形,小菜预先将正方形头像区域水平竖直方向分割为 30 份,其中橙色左上角区域为国旗绘制范围;
_drawLines(canvas, size) {
Path _path = Path();
for (int i = 0; i < size.height / _subWidth + 1; i++) {
_path.moveTo(0, _subWidth * i);
_path.lineTo(size.width, _subWidth * i);
}
for (int i = 0; i < size.width / _subWidth + 1; i++) {
_path.moveTo(_subWidth * i, 0);
_path.lineTo(_subWidth * i, size.height);
}
canvas.drawPath(_path, _paint);
Path _path2 = Path();
_path2.moveTo(size.width / 4 * 3, 0);
_path2.lineTo(size.width / 4 * 3, size.height);
_path2.moveTo(0, size.height / 2);
_path2.lineTo(size.width, size.height / 2);
canvas.drawPath(
_path2, _paint..color = Colors.deepOrange..strokeWidth = 1.0);
}
2. 确定五角星位置
根据第一步规则和绘制好的辅助线,确定五角星位置与尺寸,小菜绘制了几个空心圆来辅助定位;
_drawCircle(canvas) {
canvas.drawCircle(
Offset(5 * _subWidth, 5 * _subWidth),
3 * _subWidth,
_paint..color = Colors.deepOrange..strokeWidth = 1);
canvas.drawCircle(
Offset(10 * _subWidth, 2 * _subWidth),
_subWidth,
_paint..color = Colors.deepOrange..strokeWidth = 1);
canvas.drawCircle(
Offset(12 * _subWidth, 4 * _subWidth),
_subWidth,
_paint..color = Colors.deepOrange..strokeWidth = 1);
canvas.drawCircle(
Offset(12 * _subWidth, 7 * _subWidth),
_subWidth,
_paint..color = Colors.deepOrange..strokeWidth = 1);
canvas.drawCircle(
Offset(10 * _subWidth, 9 * _subWidth),
_subWidth,
_paint..color = Colors.deepOrange..strokeWidth = 1);
}
3. 绘制五角星
对于五角星的绘制相对复杂一些,小菜采用了最基本的 Canvas 的 drawPath 方式绘制一个封闭的五角星,其中每个角为 36 度;然后配合 dart.math 中三角函数进行各个点位的确定,这个是单纯的数学计算,小菜不做深入讨论,只是优先绘制五角星;
_drawStar(canvas, radius, rotate) {
Path _path = new Path();
double moveToX = radius + math.cos(math.pi / 10) * radius;
double moveToY = radius - math.sin(math.pi / 10) * radius;
_path.moveTo(moveToX, moveToY);
_path.lineTo(radius + math.cos(math.pi * 9 / 10) * radius,
radius - math.sin(math.pi * 9 / 10) * radius);
_path.lineTo(radius + math.cos(math.pi * 17 / 10) * radius,
radius - math.sin(math.pi * 17 / 10) * radius);
_path.lineTo(radius + math.cos(math.pi * 5 / 10) * radius,
radius - math.sin(math.pi * 5 / 10) * radius);
_path.lineTo(radius + math.cos(math.pi * 13 / 10) * radius,
radius - math.sin(math.pi * 13 / 10) * radius);
_path.close();
canvas.drawPath(
_path, _paint..style = PaintingStyle.fill..color = Color(0xFFFFDE00));
}
小菜在绘制过程是,起始位置做了一个平移,因此展示是在左上角位置,为了平移至各个位置,并且为了后期角度旋转方便,小菜在绘制时先将位置平移至左上角笛卡尔坐标系原点,在进行五角星绘制;绘制完成之后再通过 translate() 平移至各个圆心位置;
_drawStar(canvas, radius, rotate) {
...
canvas.translate(-radius, -radius);
canvas.drawPath(
_path, _paint..style = PaintingStyle.fill..color = Color(0xFFFFDE00));
canvas.translate(radius, radius);
}
_drawAllStar(canvas) {
canvas.translate(5 * _subWidth, 5 * _subWidth);
_drawStar(canvas, _subWidth * 3, 0.0);
canvas.translate(-5 * _subWidth, -5 * _subWidth);
canvas.translate(10 * _subWidth, 2 * _subWidth);
_drawStar(canvas, _subWidth, math.pi * (math.atan(3 / 5)));
canvas.translate(-10 * _subWidth, -2 * _subWidth);
canvas.translate(12 * _subWidth, 4 * _subWidth);
_drawStar(
canvas, _subWidth, math.pi * (math.atan(1 / 7)) + math.pi * 5 / 10);
canvas.translate(-12 * _subWidth, -4 * _subWidth);
canvas.translate(12 * _subWidth, 7 * _subWidth);
_drawStar(
canvas, _subWidth, math.pi * (math.atan(2 / 7)) + math.pi * 5 / 10);
canvas.translate(-12 * _subWidth, -7 * _subWidth);
canvas.translate(10 * _subWidth, 9 * _subWidth);
_drawStar(canvas, _subWidth, math.pi * (math.atan(3 / 5)));
canvas.translate(-10 * _subWidth, -9 * _subWidth);
}
五角星平移至各个位置之后就需要调整对角度的旋转了四颗小五角星均有一角尖正对大五角星的中心点,根据小菜绘制的辅助线配合 atan() 三角函数,可以获取旋转角度,这个时候就体现出辅助线的重要性了;注意:小菜建议在绘制五角星时就进行角度的旋转,这样就可以抽离出一个公共的方法,减少代码的耦合度;而旋转和平移的先后顺序,建议大家尝试之后才会有更深入的理解;
_drawStar(canvas, radius, rotate) {
...
canvas.rotate(rotate);
canvas.translate(-radius, -radius);
canvas.drawPath(
_path, _paint..style = PaintingStyle.fill..color = Color(0xFFFFDE00));
canvas.translate(radius, radius);
canvas.rotate(-rotate);
}
4. 渐变色国旗
接下来就是对渐变色国旗的绘制,小菜去掉辅助线,再通过之前常用的 ShaderMask 着色器进行处理,从左上角到右下角一个渐变透明度的设置即可;
_flagWid() => ShaderMask(
shaderCallback: (rect) => LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.white, Colors.white.withOpacity(0.0)],
stops: [0.2, 0.8],
).createShader(rect),
child: Container(
width: 300,
color: Color(0xFFDE2910),
height: 300,
child: CustomPaint(painter: ACEStarPainter())));
5. 添加头像
最后将头像通过 Stack 叠加进来即可,为了优化显示效果,通过 **** 设置一个圆角;至此小菜自定义国旗渐变头像就基本完成了;
PhysicalModel(
color: Colors.transparent,
shape: BoxShape.rectangle,
clipBehavior: Clip.antiAlias,
elevation: 6.0,
borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Stack(children: [
Container(
width: 300, height: 300,
child: Image.asset('images/icon_panda.jpeg')),
_flagWid()
]))
ACEStarPage 案例源码
整个国旗渐变色头像绘制相对简单,各个技术点小菜之前都有过介绍,其中透明度也可以随需求自由调整;如有错误,请多多指教!
祝大家国庆节快乐!
来源: 阿策小和尚