自绘组件: DoneWidget

admin7个月前未命名327

本节的我们将实现一个 DoneWidget,它可以在创建时执行一个打勾动画,效果如图10-8:

图10-8

实现代码如下:

class DoneWidget extends LeafRenderObjectWidget {
  const DoneWidget({
    Key? key,
    this.strokeWidth = 2.0,
    this.color = Colors.green,
    this.outline = false,
  }) : super(key: key);

  //线条宽度
  final double strokeWidth;
  //轮廓颜色或填充色
  final Color color;
  //如果为true,则没有填充色,color代表轮廓的颜色;如果为false,则color为填充色
  final bool outline;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderDoneObject(
      strokeWidth,
      color,
      outline,
    )..animationStatus = AnimationStatus.forward; // 创建时执行正向动画
  }

  @override
  void updateRenderObject(context, RenderDoneObject renderObject) {
    renderObject      ..strokeWidth = strokeWidth      ..outline = outline      ..color = color;
  }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

DoneWidget 有两种模式,一种是 outline 模式,该模式背景没有填充色,此时 color 表示的是轮廓线条的颜色;如果是非 outline 模式,则 color 表示填充的背景色,此时 “勾” 的颜色简单设置为白色。

接下来需要实现 RenderDoneObject,由于组件不需要响应事件,所以我们可以不用添加事件相关的处理代码;但是组件需要执行动画,因此我们可以直接使用上一节中封装的 RenderObjectAnimationMixin,具体实现代码如下:

class RenderDoneObject extends RenderBox with RenderObjectAnimationMixin {
  double strokeWidth;
  Color color;
  bool outline;

  ValueChanged<bool>? onChanged;

  RenderDoneObject(
    this.strokeWidth,
    this.color,
    this.outline,
  );

  // 动画执行时间为 300ms
  @override
  Duration get duration => const Duration(milliseconds: 300);

  @override
  void doPaint(PaintingContext context, Offset offset) {
    // 可以对动画运用曲线
    Curve curve = Curves.easeIn;
    final _progress = curve.transform(progress);

    Rect rect = offset & size;
    final paint = Paint()
      ..isAntiAlias = true
      ..style = outline ? PaintingStyle.stroke : PaintingStyle.fill //填充
      ..color = color;

    if (outline) {
      paint.strokeWidth = strokeWidth;
      rect = rect.deflate(strokeWidth / 2);
    }

    // 画背景圆
    context.canvas.drawCircle(rect.center, rect.shortestSide / 2, paint);

    paint      ..style = PaintingStyle.stroke      ..color = outline ? color : Colors.white      ..strokeWidth = strokeWidth;

    final path = Path();

    Offset firstOffset =
        Offset(rect.left + rect.width / 6, rect.top + rect.height / 2.1);

    final secondOffset = Offset(
      rect.left + rect.width / 2.5,
      rect.bottom - rect.height / 3.3,
    );

    path.moveTo(firstOffset.dx, firstOffset.dy);

    const adjustProgress = .6;
    //画 "勾"
    if (_progress < adjustProgress) {
      //第一个点到第二个点的连线做动画(第二个点不停的变)
      Offset _secondOffset = Offset.lerp(
        firstOffset,
        secondOffset,
        _progress / adjustProgress,
      )!;
      path.lineTo(_secondOffset.dx, _secondOffset.dy);
    } else {
      //链接第一个点和第二个点
      path.lineTo(secondOffset.dx, secondOffset.dy);
      //第三个点位置随着动画变,做动画
      final lastOffset = Offset(
        rect.right - rect.width / 5,
        rect.top + rect.height / 3.5,
      );
      Offset _lastOffset = Offset.lerp(
        secondOffset,
        lastOffset,
        (progress - adjustProgress) / (1 - adjustProgress),
      )!;
      path.lineTo(_lastOffset.dx, _lastOffset.dy);
    }
    context.canvas.drawPath(path, paint..style = PaintingStyle.stroke);
  }

  @override
  void performLayout() {
    // 如果父组件指定了固定宽高,则使用父组件指定的,否则宽高默认置为 25
    size = constraints.constrain(
      constraints.isTight ? Size.infinite : const Size(25, 25),
    );
  }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

上面代码很简单,但需要注意三点:

  1. 我们对动画应用了easeIn 曲线,可以看到如果在 RenderObject 中对动画应用曲线,另外读者应该也能发现,曲线的本质就是对动画的进度加了一层映射,通过不同的映射规则就可以控制动画在不同阶段的快慢。

  2. 我们重写了 RenderObjectAnimationMixin 中的 duration,该参数用于指定动画时长。

  3. adjustProgress 的作用主要是将“打勾”动画氛围两部分,第一部分是第一个点和第二个点的连线动画,这部分动画站总动画时长的 前 60%; 第二部分是第二点和第三个点的连线动画,该部分动画占总时长的后 40%。


相关文章

原生开发与跨平台技术

1. 原生开发原生应用程序是指某一个移动平台(比如iOS或安卓)所特有的应用,使用相应平台支持的开发工具和语言,并直接调用系统提供的SDK API。比如Android原生应用就是指使用Java或Kot...

Hybrid技术简介

1. H5 + 原生这类框架主要原理就是将 App 中需要动态变动的内容通过HTML5(简称 H5)来实现,通过原生的网页加载控件WebView (Android)或 WKWebView(iOS)来加...

React Native、Weex

本篇主要介绍一下 JavaScript开发 + 原生渲染 的跨平台框架原理。React Native (简称 RN )是 Facebook 于 2015 年 4 月开源的跨平台移动...

Qt Mobile

在介绍 QT 之前我们先介绍一下 “自绘UI + 原生” 跨平台技术。#1. 自绘UI + 原生我们看看最后一种跨平台技术:自绘UI + 原生。这种技术的思路是:通过在不同平台实现一个统一接口的渲染引...

Flutter出世

“千呼万唤始出来”,铺垫这么久,现在终于等到本书的主角出场了!Flutter 是 Google 发布的一个用于创建跨平台、高性能移动应用的框架。Flutter 和 Qt mobile 一样,都没有使用...

搭建Flutter开发环境

搭建Flutter开发环境

由于Flutter会同时构建Android和IOS两个平台的发布包,所以Flutter同时依赖Android SDK和iOS SDK,在安装Flutter时也需要安装相应平台的构建工具和SDK。下面我...