Flutter入门进阶之旅(二十二)Flutter自定义view

前言

在前面的章节中我们基本完成了所有对Flutter的基础知识讲解,到目前为止通读该专栏的读者应已经具备Flutter常见开发场景以及各种基础UI组件的绘制能力,但是在日常开发中业务逻辑千差万别各种场景交替存在,这时候官方提供的各种组件就很难完全满足复杂业务需求了,好在Flutter跟Native平台一样,给开发者保留了自定义VIEW的可能,开发者可以基于不同的场景,利用Flutter平台提供的API来完成高可定制化的VIEW视图来完成各种复杂的UI界面绘制。

课程目标

  • 了解并掌握Flutter自定义view的原理
  • 掌握Flutter自定义view的方式与具体操作流程
  • 利用本章节所学知识完成一个自动变化颜色的圆形自定义view

自动变化颜色的圆形自定义view效果图

自动变化颜色的圆形自定义view效果图

1.Flutter自定义view原理

自定义View顾名思义就是按照自己的意图跟想法来自己实现UI视图,熟悉Android开发的小伙伴都知道在Android平台中如果要自定义view的话,需要继承View或者ViewGroup,然后重写onDraw方法,在onDraw中用画笔(paint)在画布(canvas)上绘制相应的内容,如果要触发重绘的话则每次需调用invalidate通知刷新视图来完成。类比到Flutter平台上,在Flutter中开发者如果想完成自定义view的操作则需要继承CustomPainter,然后在它的paint方法中来绘制相应的内容,通过shouldRepaint的返回值来判断是否需要重绘。

类比分析

其实关于自定义view我们完全可以类比到现实生活中的绘画操作,我们在一张画布或者宣纸上用画笔画出我们预期的图形或者内容,只不过这里操作的对象做了一下形式上的改变,现实生活中的画笔等同于Flutter中的panit,画布等同于Flutter中的canvas,具体绘画内容等同于我们在Flutter视图层上想要绘制出的内容,绘制五彩缤纷的内容,我们又可以通过给paint设置不同的属性比如色值,粗细,抗锯齿等属性来完成。

1.1 画笔:Paint

Paint就是我们用来绘制自定义view的基础,我们可以通过给paint设置配置paint的属性来绘制不同样式的内容。
常用的属性有

  • color(设置画笔颜色)
  • strokeCap(画笔笔触类型)
  • colorFilter(颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式)
  • style(描边还是填充对应于PaintingStyle.stroke及PaintingStyle.fill)
  • strokeWidth(画笔的宽度)
  • isAntiAlias(是否抗锯齿)
  • shader(绘制渐变色常用的LinearGradient线性渐变,SweepGradient扫描渐变,RadialGradient辐射式渐变)

其他属性我就不逐一罗列了,感兴趣的读者可以结合代码自己写代码验证一下效果

1.2 画布:Canvas

有了第一步的画笔,那么下边我们就可以利用在第一步我们已经配置好的画笔在canvas上绘制具体的图形了,在Flutter中官方已经给我们提供好了一些列的绘制方法,如drawCircle绘制圆,drawRect绘制矩形,drawPath绘制任意路径。在canvas里给我们提供了大部分常用的绘制方法,方法前面均为canvas.drawxxx(xxx,paint)所示)这里的xxx其实就是下图中的内容:


在这里插入图片描述

canvas不仅仅提供大量绘制相关的方法,还提供了我们常用的平移canvas.translate,旋转canvas.rotate,裁剪canvas.clipRect等方法。

注:在整个canvas绘制的过程中系统坐标系是在左上角,向右及向下分别为x及y轴正向

1.3 绘制具体内容

在准备好了画笔(paint)跟画布(canvas)之后,下面我们只需在自定义View的
paint方法中实现具体的逻辑来完成相关绘制;如下代码利用path绘制三角形跟利用drawCircle绘制圆的示例代码如下所示:

  @override
  void paint(Canvas canvas, Size size) {
    //利用path绘制三角形
    Path path = Path();
    path.lineTo(100, 0);
    path.lineTo(0, 100);
    path.close();
    canvas.drawPath(path, _paint);

    //利用drawCircle圆心坐标点为(100, 100),半径为50的实心圆
    // canvas.drawCircle(Offset(100, 100), 50, _paint);
  }

三角形

三角形

圆心坐标点为(100,100),半径为100的实心圆

圆心坐标点为(100,100),半径为100的实心圆

1.4 处理视图是否需要刷新

在自定义view时,如果我们绘制的view不需要改变,或者说图像绘制成功之后在它存在的整个生命周期中都不再需要改变,这个时候我们只需要在shouldRepaint方法中直接返回false即可,表示当前视图不需要刷新处理。


  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }

反之则需要根据业务场景,具体去改变shouldRepaint的返回值,来通知视图是否需要刷新。

完整代码如下;

import 'package:flutter/material.dart';

/**
 * desc:自定义view
 * author: xiedong
 * date: 2021/9/2
 **/

void main() {
  runApp(MaterialApp(
    home: CustomTriangleView(),
  ));
}

class CustomTriangleView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("自定义VIEW"),
      ),
      body: CustomPaint(
        painter: TriangleView(),
      ),
    );
  }
}


class TriangleView extends CustomPainter {
  var _paint;

  TriangleView() {
    _paint = Paint()
      ..color = Colors.blueAccent //画笔颜色
      ..strokeCap = StrokeCap.round //画笔笔触类型
      ..isAntiAlias = true //是否启动抗锯齿
      ..blendMode = BlendMode.exclusion //颜色混合模式
      ..style = PaintingStyle.fill //绘画风格,默认为填充
      ..colorFilter = ColorFilter.mode(Colors.blueAccent,
          BlendMode.exclusion) //颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果,flutter中只有这个
      ..filterQuality = FilterQuality.high //颜色渲染模式的质量
      ..strokeWidth = 15.0; //画笔的宽度
  }

  @override
  void paint(Canvas canvas, Size size) {
    //利用path绘制三角形
    // Path path = Path();
    // path.lineTo(100, 0);
    // path.lineTo(0, 100);
    // path.close();
    // canvas.drawPath(path, _paint);

    //利用drawCircle圆心坐标点为(100, 100),半径为50的实心圆
    canvas.drawCircle(Offset(100, 100), 50, _paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

2.绘制颜色自动变化的圆

在开篇的时候我们给本章节提出的课程要求里提到利用所学的知识点绘制一个颜色可以自动变化的圆。那么基于此,我们首先确定自定义VIEW的状态需要动态改变,所以shouldRepaint返回值应该为true

2.1. 设置shouldRepaint返回值为true


  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

绘制函数不变,在绘制函数中,我们还是利用上述第一部分中绘制圆的逻辑代码来完成,需要改变的是,我们需要借助state来动态改变paint的颜色值。

2.2 绘制函数如下:

@override
 void paint(Canvas canvas, Size size) {
   //利用path绘制三角形
   // Path path = Path();
   // path.lineTo(100, 0);
   // path.lineTo(0, 100);
   // path.close();
   // canvas.drawPath(path, _paint);

   //利用drawCircle圆心坐标点为(100, 100),半径为50的实心圆
   print('----------图像被重绘制');
   canvas.drawCircle(Offset(100, 100), 50, _paint);
 }

2.3 利用Flutter周期函数动态修改传入自定义View中的颜色值

 @override
  void initState() {
    super.initState();
    int count = 0;
    var period = Duration(seconds: 1);
    // print('currentTime='+DateTime.now().toString());
    Timer.periodic(period, (timer) {
      print('----颜色值改变---');
      this.setState(() {
        _color = _colorArr[Random().nextInt(4)];
      });
    });
  }
  

2.4 在自定义View的构造方法中接收通过state传递过来的颜色值

在自定义view的构造方法中初始化paint颜色值由第一部分的固定值,改完通过从state传递回来的可变值,进而通过state的变化来刷新整个view的视图。


AutoChangeColorCircle(_color) {
    _paint = Paint()
      ..color = _color //画笔颜色
      ..strokeCap = StrokeCap.round //画笔笔触类型
      ..isAntiAlias = true //是否启动抗锯齿
      ..blendMode = BlendMode.exclusion //颜色混合模式
      ..style = PaintingStyle.fill //绘画风格,默认为填充
      ..colorFilter = ColorFilter.mode(Colors.blueAccent,
          BlendMode.exclusion) //颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果,flutter中只有这个
      ..filterQuality = FilterQuality.high //颜色渲染模式的质量
      ..strokeWidth = 15.0; //画笔的宽度
  }

效果如下


自动变化颜色的圆形自定义view效果图

完整代码如下:


import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';

/**
 * desc:自定义view
 * author: xiedong
 * date: 2021/9/2
 **/

void main() {
  runApp(MaterialApp(
    // home: CustomTriangleView(),
    home: AutoChangeColorCircleView(),
  ));
}




class AutoChangeColorCircleView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => ViewState();
}

class ViewState extends State<AutoChangeColorCircleView> {
  var _colorArr = [
    Colors.amberAccent,
    Colors.blue,
    Colors.deepOrange,
    Colors.cyan,
    Colors.black,
    Colors.deepPurple,
  ];
  var _color;

  @override
  void initState() {
    super.initState();
    int count = 0;
    var period = Duration(seconds: 1);
    // print('currentTime='+DateTime.now().toString());
    Timer.periodic(period, (timer) {
      print('----颜色值改变---');
      this.setState(() {
        _color = _colorArr[Random().nextInt(4)];
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("自定义VIEW"),
      ),
      body: CustomPaint(
        painter: AutoChangeColorCircle(_color),
      ),
    );
  }
}

class AutoChangeColorCircle extends CustomPainter {
  var _paint;

  AutoChangeColorCircle(_color) {
    _paint = Paint()
      ..color = _color //画笔颜色
      ..strokeCap = StrokeCap.round //画笔笔触类型
      ..isAntiAlias = true //是否启动抗锯齿
      ..blendMode = BlendMode.exclusion //颜色混合模式
      ..style = PaintingStyle.fill //绘画风格,默认为填充
      ..colorFilter = ColorFilter.mode(Colors.blueAccent,
          BlendMode.exclusion) //颜色渲染模式,一般是矩阵效果来改变的,但是flutter中只能使用颜色混合模式
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果,flutter中只有这个
      ..filterQuality = FilterQuality.high //颜色渲染模式的质量
      ..strokeWidth = 15.0; //画笔的宽度
  }

  @override
  void paint(Canvas canvas, Size size) {
    //利用path绘制三角形
    // Path path = Path();
    // path.lineTo(100, 0);
    // path.lineTo(0, 100);
    // path.close();
    // canvas.drawPath(path, _paint);

    //利用drawCircle圆心坐标点为(100, 100),半径为50的实心圆
    print('----------图像被重绘制');
    canvas.drawCircle(Offset(100, 100), 50, _paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

完整代码详见Github Flutter入门进阶之旅专栏代码

在Flutter中除了继承CustomPainter重新绘制图形来完成自定义view,在某些简单的场景下,我们也可以利用组合Flutter现有的组件来完成自定义view,比如在上一篇博文中我们讲到的 Flutter入门进阶之旅 - Flutter课程表View就是通过组合Flutter中现有的组件来完成自定义view,感兴趣的读者可以从我的专栏中找到该文章,对比一下通过两种不同的方式来完成自定义Flutter的优缺点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,846评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,146评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,789评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,656评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,467评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,870评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,500评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,790评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,828评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,628评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,449评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,881评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,077评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,376评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,879评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,089评论 2 338

推荐阅读更多精彩内容