Flutter 2:在没有 ShaderMask 的情况下创建渐变图标

Flutter 2: Create Gradient Icon without ShaderMask

我正在寻找一种不使用 ShaderMask() 来创建渐变图标的方法。

为什么不用 ShaderMask?

因为调用 saveLayer() 分配了一个屏幕外缓冲区。将内容绘制到屏幕外缓冲区可能会触发渲染目标切换,这在旧 GPU 中特别慢。 (official flutter documentation)

这里是我用 ShaderMask 制作的 GradientIcon。

import 'package:flutter/material.dart';

class GradientIcon extends StatefulWidget {
  final IconData? icon;
  final double? size;
  final Gradient? gradient;

  const GradientIcon({
    @required this.icon,
    @required this.size,
    @required this.gradient,
    Key? key
  }) : super(key: key);

  @override
  _GradientIconState createState() => _GradientIconState();
}

class _GradientIconState extends State<GradientIcon> {
  double size = 0;
  static const iconSizeMultiplier = 1.2;

  Shader? shaderFromGradient;

  @override
  void initState() {
    super.initState();

    if (widget.size != null)
      size = widget.size! * iconSizeMultiplier;
  }

  @override
  Widget build(BuildContext context) {
    return ShaderMask(
      child: Icon(
        widget.icon,
        size: size,
        color: Colors.white,
      ),
      shaderCallback: (Rect bounds) {
        return widget.gradient!.createShader(Rect.fromLTRB(0, 0, size, size));
      },
    );
  }
}

所以我的问题是:有没有一种方法可以在不调用 saveLayer() 的情况下渲染渐变图标,也就是不调用 ShaderMask()

import 'package:flutter/material.dart';

class GradientIcon extends StatelessWidget {
  final IconData icon;
  final Gradient gradient;
  final double size;

  const GradientIcon(
    this.icon,
    this.gradient,
    {
      this.size = 24,
      Key? key
    }
  ) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: CustomPaint(
        size: Size(size, size),
        painter: _GradientIconPainter(
          icon: icon,
          gradient: gradient,
          iconSize: size
        ),
      )
    );
  }
}

class _GradientIconPainter extends CustomPainter {
  final IconData? icon;
  final Gradient? gradient;
  final double? iconSize;

  _GradientIconPainter({
    Listenable? repaint,
    @required this.icon,
    @required this.gradient,
    @required this.iconSize
  }) : super(repaint: repaint);

  @override
  void paint(Canvas canvas, Size size) {
    final Paint _gradientShaderPaint = Paint()
      ..shader = gradient!.createShader(
          Rect.fromLTWH(0.0, 0.0, size.width, size.height)
      );

    final TextPainter _textPainter = TextPainter(
      textDirection: TextDirection.ltr,
      text: TextSpan(
        text: String.fromCharCode(icon!.codePoint),
        style: TextStyle(
          foreground: _gradientShaderPaint,
          fontFamily: icon!.fontFamily,
          fontSize: iconSize
        ),
      )
    );
    _textPainter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );

    final xCenter = (size.width - _textPainter.width) / 2;
    final yCenter = (size.height - _textPainter.height) / 2;
    final offset = Offset(xCenter, yCenter);
    _textPainter.paint(canvas, offset);
  }

  @override
  bool shouldRepaint(_GradientIconPainter oldDelegate) {
    return icon != oldDelegate.icon || gradient != oldDelegate.gradient ||
    iconSize != oldDelegate.iconSize;
  }
}