The user can control both the opacity
and BlendMode
used by the widgets and change them on the fly.
When there is no Opacity
widget, it works, you can visually see the different BlendMode
apply.
As soon as an Opacity
widget is applied, the BlendMode
update doesn't seem to do anything anymore.
There isn't a built-in widget that can handle BlendMode
, so I'm using a custom RenderProxyBox
that applies the BlendMode
during painting.
void paint(context, offset) {
context.canvas.save();
context.canvas.clipRect(offset & size);
context.canvas.saveLayer(
offset & size,
Paint()..blendMode = blendMode,
);
super.paint(context, offset);
context.canvas.restore();
context.canvas.restore();
}
A complete code example that demonstrates the problem.
Click the floating action button to change the blend mode, it'll work, check the checkbox so that the Opacity widget is applied, if you now try to update the blend mode once again, it will no longer re-render correctly.
Note: I'm using ColoredBox
but in reality, the widgets can be anything (Text
, Image
, ...).
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'BlendMode AND Opacity',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage();
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
BlendMode blendMode = BlendMode.difference;
bool hasOpacity = false;
void _changeBlendMode() {
setState(() {
if (blendMode == BlendMode.difference) {
blendMode = BlendMode.srcOver;
} else {
blendMode = BlendMode.difference;
}
});
}
void _setOpacity(bool? opacityOn) {
setState(() {
hasOpacity = opacityOn ?? false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: [
const Positioned(
left: 40,
top: 40,
child: SizedBox(
width: 100,
height: 100,
child: ColoredBox(color: Colors.red),
),
),
Positioned(
left: 30,
top: 30,
child: SizedBox(
width: 50,
height: 50,
child: BlendMask(
blendMode: blendMode,
child: (hasOpacity)
? const Opacity(
opacity: 0.5,
child: ColoredBox(color: Colors.green),
)
: const ColoredBox(color: Colors.green),
),
),
),
Positioned(
left: 30,
top: 150,
child: Checkbox(
value: hasOpacity,
onChanged: _setOpacity,
),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _changeBlendMode,
child: const Icon(Icons.add),
),
);
}
}
class BlendMask extends SingleChildRenderObjectWidget {
final BlendMode blendMode;
const BlendMask({
required this.blendMode,
super.key,
super.child,
});
@override
RenderObject createRenderObject(context) {
return RenderBlendMask(blendMode);
}
@override
void updateRenderObject(BuildContext context, RenderBlendMask renderObject) {
super.updateRenderObject(context, renderObject);
renderObject.blendMode = blendMode;
}
}
class RenderBlendMask extends RenderProxyBox {
BlendMode blendMode;
RenderBlendMask(this.blendMode);
@override
void paint(context, offset) {
context.canvas.save();
context.canvas.clipRect(offset & size);
context.canvas.saveLayer(
offset & size,
Paint()..blendMode = blendMode,
);
super.paint(context, offset);
context.canvas.restore();
context.canvas.restore();
}
}