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

824 阅读8分钟

前言

在前面的章节中我们基本完成了所有对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的优缺点。