0

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();
  }
}
Matthiee
  • 431
  • 4
  • 14

0 Answers0