如何使用 CustomPainter 将 ColorFilter 应用到 Stack 中的图层

问题描述 投票:0回答:1

我想通过在其顶部应用另一个 CustomPainter 来使 CustomPainter 的部分变暗,同时不影响其下方的图层。

基本结构如下:

Stack(
  children: [
    CustomPaint(
      painter: BottomPaint(), // unaffected
      child: const SizedBox.expand(),
    ),
    CustomPaint(
      painter: MiddlePaint(), // should be partially darkened
      child: const SizedBox.expand(),
    ),
    CustomPaint(
      painter: TopPaint(), // shape here should be applied as darkening mask to MiddlePaint
      child: const SizedBox.expand(),
    ),
  ],
),

最终结果应该看起来或多或少像这样,其中通过在

MiddlePaint
TopPaint
中绘制矩形来实现黑暗区域。

enter image description here

我一直在测试各种混合模式来实现它,以及canvas.saveLayer(),但到目前为止我尝试过的方法都不是令人满意的。

目前,混合模式方法允许我将堆栈中的两个图层变暗,如下例所示:

enter image description here

有没有一种方法可以通过在单独的 CustomPainter 或同一个 CustomPainter 中绘制矩形来仅使一个 CustomPainter 变暗?

完整示例:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Stack(
          children: [
            CustomPaint(
              painter: BottomPaint(), // should be unaffected
              child: const SizedBox.expand(),
            ),
            CustomPaint(
              painter:
                  MiddlePaint(), // should be darkened where TopPaint is applied
              child: const SizedBox.expand(),
            ),
            CustomPaint(
              painter:
                  TopPaint(), // shape here should be applied as darkening mask to MiddlePaint
              child: const SizedBox.expand(),
            ),
          ],
        ),
      ),
    );
  }
}

class BottomPaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final fullRect = Offset.zero & size;
    canvas.saveLayer(fullRect, Paint());
    canvas.drawRect(
      Rect.fromLTWH(0, size.height / 3, size.width, size.height / 3),
      Paint()..color = Colors.red,
    );
    canvas.restore();
  }

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

class MiddlePaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final fullRect = Offset.zero & size;

    final path = Path()
      ..moveTo(0, size.height)
      ..lineTo(size.width / 2, size.height / 2)
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height);
    canvas.drawPath(
      path,
      Paint()..color = Colors.blue,
    );
  }

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

class TopPaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final numberOfSections = BlendMode.values.length;
    final sectionWidth = size.width / numberOfSections;
    final sectionHeight = size.height / 2;

    for (var i = 0; i < BlendMode.values.length; i++) {
      final blendMode = BlendMode.values[i];
      final x = i * sectionWidth;
      final y = size.height - sectionHeight;
      final rect = Rect.fromLTWH(x, y, sectionWidth, sectionHeight);
      canvas.drawRect(
        rect,
        Paint()
          ..color = Colors.black26
          ..blendMode = blendMode,
      );

      printName(blendMode, canvas, x, sectionWidth, y, sectionHeight);
    }
  }

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

/// name of the blend mode below
void printName(
  BlendMode blendMode,
  Canvas canvas,
  double x,
  double sectionWidth,
  double y,
  double sectionHeight,
) {
  final textPainter = TextPainter(
    text: TextSpan(
      text: blendMode.name,
      style: const TextStyle(color: Colors.white),
    ),
    textDirection: TextDirection.ltr,
  );
  textPainter.layout();
  textPainter.paint(
    canvas,
    Offset(
      x + sectionWidth / 2 - textPainter.width / 2,
      y + sectionHeight - 20,
    ),
  );
}
flutter canvas
1个回答
0
投票

MiddlePaintTopPaint上应用ShaderMask应该可以让您达到所需的效果。 ShaderMask 中的渐变颜色并不重要。我们只是用它们来创建一个实体层。重要的是混合模式,它应该是仅绘制目标图像的任何模式,例如: blendMode: BlendMode.dstInblendMode: BlendMode.dst

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Stack(
          children: [
            CustomPaint(
              painter: BottomPaint(), // should be unaffected
              child: const SizedBox.expand(),
            ),
            ColorBlendMask(
              color: Colors.black,
              blendMode: BlendMode.dstIn,
              child: Stack(
                children: [
                  CustomPaint(
                    painter:
                        MiddlePaint(), // should be darkened where TopPaint is applied
                    child: const SizedBox.expand(),
                  ),
                  CustomPaint(
                    painter:
                        TopPaint(), // shape here should be applied as darkening mask to MiddlePaint
                    child: const SizedBox.expand(),
                  ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

ColorBlendMask小部件只是ShaderMask的包装:

class ColorBlendMask extends StatelessWidget {
  const ColorBlendMask({
    super.key,
    required this.child,
    required this.color,
    required this.blendMode,
  });

  final Widget child;
  final Color color;
  final BlendMode blendMode;

  @override
  Widget build(BuildContext context) {
    return ShaderMask(
      blendMode: blendMode,
      shaderCallback: (bounds) => LinearGradient(
        colors: [color, color],
        stops: const [0.0, 1.0],
      ).createShader(bounds),
      child: child,
    );
  }
}

TopPaint 图层的混合模式应该是:

    canvas.drawRect(
      rect,
 Paint()
        ..color = Colors.black26
 ..blendMode = BlendMode.srcATop,
    );

最终结果应如下所示:

result

© www.soinside.com 2019 - 2024. All rights reserved.