我想通过在其顶部应用另一个 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
中绘制矩形来实现黑暗区域。
我一直在测试各种混合模式来实现它,以及canvas.saveLayer(),但到目前为止我尝试过的方法都不是令人满意的。
目前,混合模式方法允许我将堆栈中的两个图层变暗,如下例所示:
有没有一种方法可以通过在单独的 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,
),
);
}
在MiddlePaint和TopPaint上应用ShaderMask应该可以让您达到所需的效果。 ShaderMask 中的渐变颜色并不重要。我们只是用它们来创建一个实体层。重要的是混合模式,它应该是仅绘制目标图像的任何模式,例如: blendMode: BlendMode.dstIn 或 blendMode: 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,
);
最终结果应如下所示: