前言

「万物之中,希望至美」,《肖生克的救赎》这句话一直记在心里,不论生活多么不易,心有希望,生活一定会越来越好。

「 Hope is a good thing , maybe the best of things , and no good thing ever dies . 」

Flutter 出现已经有一段时间了,搭好环境由于忙其他事就搁置了,恰好这次参与公司 Flutter 项目开发,是时候拾起来了,前两天了解了 Dart 语言,今天刚好看到控件这部分,于是简单写了个“蛛网”控件。

效果图如下:

初步分析

从效果图上可以分析得到,“蛛网” 由简单的线条组成,那需要绘制线条。在 Android 中,可以通过自定义 View ,在 onDraw 方法中调用 canvas.drawLine;或者调用 canvas.drawPath 绘制线条。那么在 Flutter 又该怎样绘制线条?

在 Android 中有 View 提供绘制;同样在 Flutter 中有 CustomPainter 提供绘制,源码中是这么介绍的:

///  * [Canvas], the class that a custom painter uses to paint.
///  * [CustomPaint], the widget that uses [CustomPainter], and whose sample
///    code shows how to use the above `Sky` class.
///  * [RadialGradient], whose sample code section shows a different take
///    on the sample code above.
abstract class CustomPainter extends Listenable {

大概意思是:画家用来自定义绘画的类,暂且把它理解成 View 。你可以这么来使用它:

class NetView extends CustomPainter{@overridevoid paint(Canvas canvas, Size size) {// TODO: implement paint}@overridebool shouldRepaint(CustomPainter oldDelegate) {// TODO: implement shouldRepaintreturn null;}
}

继承 CustomPainter ,重写 paint 与 shouldRepaint 方法。paint 方法类似 Android 中的 onDraw 方法,但多了一个 Size 参数,来看下 Size 类的定义:

  const Size(double width, double height) : super(width, height);

构造参数 width ,height 表示绘制区域的宽高,可以理解成画布大小。Size 需要从外部调用的地方传入,而 Android 需要在 onMeasure 进行测量,Flutter 的方式更加灵活。

shouldRepaint 从方法名就可以知道,用于控制重绘,为了提高效率,一般可以这么写:

  @overridebool shouldRepaint(CustomPainter oldDelegate) {// TODO: implement shouldRepaintreturn oldDelegate != this;}

那么我们就可以在 paint(Canvas canvas, Size size) 方法中绘制我们想要的形状。

构思代码

蛛网由底部的网状路径以及顶部的不规则覆盖物组成。那可以分成两部分,第一部分绘制底部网状;第二部分绘制顶部覆盖物。

观察底部网状路径由多个正多边形组成(边长递增),那么可以拆分先绘制一个正多边形。


在 Flutter 的 canvas 中提供了 drawPath(Path path, Paint paint) 方法绘制路径,与 Android 的使用方式一样。

通过 Size 可以拿到整个控件区域的大小,那么正多边形的中点坐标就很容易获取到:

    mCenterX = size.width / 2;mCenterY = size.height / 2;

为了方便,这里可以把正多边形看成圆内切,想到圆,就应该想到圆的半径,同样根据控件大小获取半径:

 radius = mCenterX / mEdgeSize

最后需要获取到圆内切正多边形的顶点,简单的数学公式:

  double x = mCenterX + radius * cos(degToRad(angle * j));double y = mCenterY + radius * sin(degToRad(angle * j));

比较尴尬的是,Flutter 中并没有 Math.toRadians 函数,那只能自己撸一个,就像这样:

  num degToRad(num deg) => deg * (pi / 180.0);num radToDeg(num rad) => rad * (180.0 / pi);

绘制多个正多边形,只需要改变 radius 的值:

  //  mEdgeSize 表示多边形的边数 i + 1 radius = mCenterX / mEdgeSize * (i + 1);

到此,底部的网状路径绘制差不多,顶部的覆盖物类似,随机改变 radius 的长度:

  double value = (random.nextInt(10) + 1) / 10;double x = mCenterX + radiusMaxLimit * cos(degToRad(angle * i)) * value;double y = mCenterY + radiusMaxLimit * sin(degToRad(angle * i)) * value;

接着看看代码如何写。

起名字

起一个接地气的名字,能够让你眼前一亮,就叫 SpiderView

编写代码

SpiderView类

先来看看 SpiderView 类的成员变量:

  Paint mPaint;// 覆盖物画笔Paint mCoverPaint;// 文本画笔Paint mTextPaint;Path mPath;// 绘制边数默认为6int mEdgeSize = 6;final double CIRCLE_ANGLE = 360;// 整个绘制区域的中点坐标double mCenterX = 0;double mCenterY = 0;

画笔,路劲初始化:

  SpiderView(this.mEdgeSize) {// 初始化画笔mPaint = new Paint();mPaint.color = randomRGB();// 设置抗锯齿mPaint.isAntiAlias = true;// 样式为描边mPaint.style = PaintingStyle.stroke;mPath = new Path();mCoverPaint = new Paint();mCoverPaint.isAntiAlias = true;mCoverPaint.style = PaintingStyle.fill;mCoverPaint.color = randomARGB();mTextPaint = new Paint();mTextPaint.isAntiAlias = true;mTextPaint.style = PaintingStyle.fill;mTextPaint.color = Colors.blue;}

paint 方法:

  @overridevoid paint(Canvas canvas, Size size) {// TODO: implement paintmCenterX = size.width / 2;mCenterY = size.height / 2;// 图层 防止刷新属性结构canvas.save();drawSpiderEdge(canvas);drawCover(canvas);drawText(canvas);canvas.restore();}

绘制底部网状路劲,代码相对简单,有疑问请留言:

  /*** 绘制边线*/void drawSpiderEdge(Canvas canvas) {double angle = CIRCLE_ANGLE / mEdgeSize;double radius = 0;double radiusMaxLimit = mCenterX > mCenterY ? mCenterX : mCenterY;for (int i = 0; i < mEdgeSize; i++) {mPath.reset();radius = radiusMaxLimit / mEdgeSize * (i + 1);for (int j = 0; j < mEdgeSize + 1; j++) {// 移动if (j == 0) {mPath.moveTo(mCenterX + radius, mCenterY);} else {double x = mCenterX + radius * cos(degToRad(angle * j));double y = mCenterY + radius * sin(degToRad(angle * j));mPath.lineTo(x, y);}}mPath.close();canvas.drawPath(mPath, mPaint);}drawSpiderAxis(canvas, radiusMaxLimit, angle);}/*** 绘制轴线*/void drawSpiderAxis(Canvas canvas, double radius, double angle) {for (int i = 0; i < mEdgeSize; i++) {mPath.reset();mPath.moveTo(mCenterX, mCenterX);double x = mCenterX + radius * cos(degToRad(angle * i));double y = mCenterY + radius * sin(degToRad(angle * i));mPath.lineTo(x, y);canvas.drawPath(mPath, mPaint);}}

绘制顶部覆盖物:

  /*** 绘制覆盖区域*/void drawCover(Canvas canvas) {mPath.reset();Random random = new Random();double angle = CIRCLE_ANGLE / mEdgeSize;double radiusMaxLimit = min(mCenterY, mCenterY);for (int i = 0; i < mEdgeSize; i++) {double value = (random.nextInt(10) + 1) / 10;double x = mCenterX + radiusMaxLimit * cos(degToRad(angle * i)) * value;double y = mCenterY + radiusMaxLimit * sin(degToRad(angle * i)) * value;if (i == 0) {mPath.moveTo(x, mCenterY);} else {mPath.lineTo(x, y);}}mPath.close();canvas.drawPath(mPath, mCoverPaint);}

源码地址

总结

Flutter 初学,希望对大家有所帮助,如果你有更好的方案,请留言哟。

想了解更多 Flutter 自定义控件,请关注「控件人生」公众号。每日有干货推送,还有现金红包发放,原创不易,戳戳手指扫码关注。

Flutter自定义控件第一式,炫酷“蛛网”控件相关推荐

  1. WPF实现炫酷Loading控件

    原文: WPF实现炫酷Loading控件 Win8系统的Loading效果还是很不错的,网上也有人用CSS3等技术实现,研究了一下,并打算用WPF自定义一个Loading控件实现类似的效果,并可以让用 ...

  2. 文字居中 qt_Qt编写自定义控件11-设备防区按钮控件

    前言 在很多项目应用中,需要根据数据动态生成对象显示在地图上,比如地图标注,同时还需要可拖动对象到指定位置显示,能有多种状态指示,安防领域一般用来表示防区或者设备,可以直接显示防区号,有多种状态颜色指 ...

  3. Flutter中Row中的子控件左右两端对齐

    Flutter中Row中的子控件左右两端对齐 Container(// padding: EdgeInsets.only(left: 20, right: 20),margin: EdgeInsets ...

  4. Android自定义控件之轮播图控件

    背景 最近要做一个轮播图的效果,网上看了几篇文章,基本上都能找到实现,效果还挺不错,但是在写的时候感觉每次都要单独去重新在Activity里写一堆代码.于是自己封装了一下.这里只是做了下封装成一个控件 ...

  5. 自定义控件:等比例显示控件RatioLayout

    我们经常碰到服务器返回的图片比例大小是一样的,但是分辨力却是不一样的.这时候,就会遇到显示效果的问题.例如,图1和图2都是宽高比例相等,但是分辨率大小不一样的图片,应该按照比例显示,使用等比例显示控件 ...

  6. c# imager让图片有圆角unity_Qt编写自定义控件24-图片轮播控件

    一.前言 上一篇文章写的广告轮播控件,采用的传统widget堆积设置样式表做的,这次必须要用到更高级的QPainter来绘制了,这个才是最高效的办法,本控件参考雨田哥的轮播控件,经过大规模的改造而成, ...

  7. 轮播高度_Qt编写自定义控件24-图片轮播控件

    一.前言 上一篇文章写的广告轮播控件,采用的传统widget堆积设置样式表做的,这次必须要用到更高级的QPainter来绘制了,这个才是最高效的办法,本控件参考雨田哥的轮播控件,经过大规模的改造而成, ...

  8. Flutter基础—根据用户输入改变控件

    之前使用的都是无状态控件,无状态控件从父控件接收参数,以final成员变量存储.当一个控件被build时,它使用这些存储的值作为创建控件的参数. 为了构建更友好的体验,比如以更有趣的方式对用户的输入做 ...

  9. 如何利用 Android 自定义控件实现炫酷的动画?|CSDN 博文精选

    作者 | u012551350 本文精选自 CSDN 博客,已获作者授权 「知足常乐」,很多人不满足现状,各种折腾,往往舍本逐末,常乐才能少一分浮躁,多一分宁静.近期在笔者身上发生了许多事情,心态也发 ...

最新文章

  1. ASP.NET Core分布式项目实战(Consent 确认逻辑实现)--学习笔记
  2. 64 求1+2+3+...+n(发散思维能力 )
  3. Rabbit and Grass【博弈】
  4. [Diary]6.12
  5. 统计字符串中每个字符的个数_C++程序设计——统计数字字符个数
  6. html不存在模板,模板文件不存在,无法解析文档的解决方法
  7. python xlwt追加_python excel写入及追加写入
  8. Sofa memcached client
  9. 双色F3.75LED点阵屏中保护电路74HC04的作用
  10. mysql怎么将成绩划分等级_数据库mysql中case如何给成绩划分等级?
  11. js+css制作导航栏下划线跟随动画,App+H5点击效果
  12. John McCarthy:人工智能之父
  13. jdbc mysql例子
  14. KaTex的一个简单例子
  15. 正则中$1、$2的应用--日期格式化
  16. 单点登录、域用户、常规登录、AD域
  17. 大数据正在改变我们的生活
  18. 2006-10-01 十一皖南单车行
  19. python简单命令_python常用命令有哪些
  20. 胆囊有排毒鸿蒙那,胆生病,别怕!身上有反射区,专“治”胆病,结石、炎症绕道走...

热门文章

  1. 2019最新妙味课堂Javascript实炼高级专题项目实战(完整)
  2. 粤嵌教你从C轻松到C++(一)
  3. 智慧餐饮远程监控解决方案,让您吃的放心吃的安心
  4. JavaScript 正则表达式实用实战
  5. what if? serious scientific answers to absurd hypothetical questions
  6. 分布式系统里session同步
  7. CC2540开发板学习笔记(七)—— 睡眠唤醒
  8. 在DIV+CSS排版中新闻列表的制作方法
  9. 50MHz生成400Hz详解
  10. 计算机辅助制造实验二,《计算机辅助设计与制造》课程实验指导书