DecratedBox 介绍

在布局完成后,就进入到了绘制阶段。DecratedBox 有一个 position 参数,是 DecorationPosition 类型枚举,描述了在什么位置应用 decoration。

/// Where to paint a box decoration.
enum DecorationPosition {
  /// Paint the box decoration behind the children.
  background,

  /// Paint the box decoration in front of the children.
  foreground,
}

position 决定了 decration 是在 child 之前绘制还是在 child 之后绘制。

DecratedBox 只是包了个 widget 的皮,真正如何绘制一般是由 BoxDecoration(也可以由其它 decoration 的子类) 来决定的。BoxDecoration 是一套不可变的配置,描述了如何绘制一个 box。

在 DecratedBox 对应的 renderBox 的 pain 方法中拿到 BoxDecoration 描述的 BoxPainter 进行绘制。

@override
  void paint(PaintingContext context, Offset offset) {
    assert(size.width != null);
    assert(size.height != null);
    // 拿到 BoxPainter 的实例
    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
   ...

BoxPainter 的实例携带着 BoxDecoration 的信息,绘制的时候会根据这些信息进行绘制。

通过 BoxPainter 的源代码,先了解下 BoxPainter 的绘制过程。

BoxPainter 的绘制过程

第一步 paint boxShadow

BoxShadow 是最先被绘制的。

参数 boxShadow 是一个 List 可以添加多个,效果叠加在一起。

第二步 paint BackgroundColor

color、gradient、blendMode 都是在这个时候应用。

第三步 paint BackgroundImage

参数 image 是一个 DecorationImage。 DecorationImage 和 BoxDecoration 的作用一样,都是配置。真是画图的是 paintImage。

参数 shape 是枚举 BoxShape 有两个值可以选

enum BoxShape {
  rectangle,
  circle,
}

在绘制背景图的时候,shape 参数决定 clipPath

switch (_decoration.shape) {
      case BoxShape.circle:
        assert(_decoration.borderRadius == null);
        final Offset center = rect.center;
        final double radius = rect.shortestSide / 2.0;
        final Rect square = Rect.fromCircle(center: center, radius: radius);
        clipPath = Path()..addOval(square);
        break;
      case BoxShape.rectangle:
        if (_decoration.borderRadius != null) {
          clipPath = Path()..addRRect(_decoration.borderRadius!.resolve(configuration.textDirection).toRRect(rect));
        }
        break;
    }
   _imagePainter!.paint(canvas, rect, clipPath, configuration);

第四步 绘制 border

参数 border,参数 shape,参数 borderRadius 会参与 border 的绘制。

_decoration.border?.paint(
      canvas,
      rect,
      shape: _decoration.shape,
      borderRadius: _decoration.borderRadius?.resolve(textDirection),
      textDirection: configuration.textDirection,
    );

了解了整个绘制过程,我们来实践一下。

演示 BoxPainter 绘制过程

paint boxShadow

【绘制 widget】Flutter DecratedBox_Android

先画一个 shadow。画出来的 shadow 并不是只在边缘画,而是在整个区域绘制。

paint color

【绘制 widget】Flutter DecratedBox_背景图_02

画上 color 后,会覆盖住主体部分,只漏出边缘。这下 shadow 的效果就出来了。

color 和 gradient 同时存在,color 被忽略

paint image

【绘制 widget】Flutter DecratedBox_背景图_03

加上背景图后,color或 gradinet 会被覆盖。

【绘制 widget】Flutter DecratedBox_Flutter_04

加上 shape 参数后,可以 clip 整个 decoration。 shape 参数不能 clip child,这样做也是符合预期的,如果把 child 也 clip了,那作用就是装饰了。

paint border

【绘制 widget】Flutter DecratedBox_背景图_05

border 也会被 shape 影响,在背景图的上面的绘制。是从外向内画,border 如果够粗,可以完全覆盖背景图。

DecratedBox 可以选择的 decration 类

一般情况下,我们选用的是 BoxDecoration。 查看文档发现 ,BoxDecoration 还有三个兄弟,是不是也可以用呢?

  • BoxDecoration
  • FlutterLogoDecoration
  • ShapeDecoration
  • UnderlineTabIndicator

FlutterLogoDecoration 是用来画 FlutterLogo 的,pass。
UnderlineTabIndicator 是用来画 tab 下面的装饰的,pass。

ShapeDecoration 可以用,而且官方还给出了示例。border 可以叠加

【绘制 widget】Flutter DecratedBox_背景图_06

Container(
  decoration: ShapeDecoration(
    color: Colors.white,
    shape: Border.all(
      color: Colors.red,
      width: 8.0,
    ) + Border.all(
      color: Colors.green,
      width: 8.0,
    ) + Border.all(
      color: Colors.blue,
      width: 8.0,
    ),
  ),
  child: const Text('RGB', textAlign: TextAlign.center),
)

参数 shape 的类型是 ShapeBorder border 的继承关系 ShapeBorder > BoxBorder > border

BoxBorder 还有两个兄弟 InputBorder 和 OutlinedBorder。OutlinedBorder 是抽象类,他下面的子类都可以用。

  • BeveledRectangleBorder
  • CircleBorder
  • ContinuousRectangleBorder
  • MaterialStateOutlinedBorder
  • RoundedRectangleBorder
  • StadiumBorder

这些类在 ClipPath 里面也可以用。

Boxdecration 只能画圆形和圆角矩形,如果需要它形状,就得用 ShapeDecration 了。如果 Boxdecration 和 ShapeDecration 都不能满足,还可以自己写 decration。

自定义 decration

自定义 decration 一共分两步。第一步, exteds Decration,第二步 extends BoxPainter。只要 cavas 熟练,这里就没什么难度了。

class MyDecration extends Decoration {
  @override
  BoxPainter createBoxPainter([VoidCallback? onChanged]) {
    return MyBoxPainter(this);
  }
}

class MyBoxPainter extends BoxPainter {
  final MyDecration _decration;
  MyBoxPainter(this._decration);
  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    // 在这随便画点什么
  }
}