如何在Flutter中使用混合模式(mix blend mode)?

16

我尝试了似乎只适用于颜色的混合模式,即colorblendmode。是否有办法实现CSS中的mix-blend-mode

  Stack(
        children: <Widget>[
          Image.asset(
            "asset/text.PNG",
            height: double.maxFinite,
            width: double.maxFinite,
            fit: BoxFit.fitHeight,
            color: Colors.red,
            colorBlendMode: BlendMode.multiply,
          ),
          Image.asset("asset/face.jpg",
              width: double.maxFinite,
              fit: BoxFit.fitHeight,
              color: Colors.red,
              colorBlendMode: BlendMode.multiply),
        ],
      ),

这将导致类似于以下的结果: 上述代码的输出 我想要获得的是 CSS的输出

不知道。但也许你可以尝试不同的BlendMode.<type>组合。我的意思是,不仅仅是使用multiply。另一种方式可能是尝试OverlayPainter和BlendMode。 - JRamos29
我认为我有一个使用RenderElements和BlendModes的解决方案。稍后应该会发布。 - Wilson Wilson
2个回答

19

使用 RenderProxyBox 和绘图技术,我能够在不异步加载图像的情况下,在 Flutter 代码中精确地重新创建 CSS 网站上的示例。

CSS 中的图像(左)与 Flutter 中的图像(右)。

Blendmode CSS vs Flutter

请阅读我在这方面撰写的文章

首先,创建一个BlendMask SingleChildRenderObject,它创建了一个名为RenderBlendMask的RenderProxyBox渲染对象。使用BlendMode和不透明度对子元素进行绘制。

import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class BlendMask extends SingleChildRenderObjectWidget {
  final BlendMode blendMode;
  final double opacity;

  BlendMask({
    @required this.blendMode,
    this.opacity = 1.0,
    Key key,
    Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(context) {
    return RenderBlendMask(blendMode, opacity);
  }

  @override
  void updateRenderObject(BuildContext context, RenderBlendMask renderObject) {
    renderObject.blendMode = blendMode;
    renderObject.opacity = opacity;
  }
}

class RenderBlendMask extends RenderProxyBox {
  BlendMode blendMode;
  double opacity;

  RenderBlendMask(this.blendMode, this.opacity);

  @override
  void paint(context, offset) {
    context.canvas.saveLayer(
        offset & size,
        Paint()
          ..blendMode = blendMode
          ..color = Color.fromARGB((opacity * 255).round(), 255, 255, 255));

    super.paint(context, offset);

    context.canvas.restore();
  }
}

现在,要混合两个小部件(不仅限于图像),只需使用堆栈将您想要混合的小部件放在另一个小部件上方,并在其中包装您的BlendMode。

class ImageMixer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox.expand(
          child: Stack(
        children: [
          SizedBox.expand(
            child: Image.asset(
              'images/sky.jpg',
            ),
          ),
          BlendMask(
            opacity: 1.0,
            blendMode: BlendMode.softLight,
            child: SizedBox.expand(
              child: Image.asset(
                'images/monkey.jpg',
              ),
            ),
          ),
        ],
      )),
    );
  }
}

那会生成上面的图片,它的工作方式与CSS示例完全相同。

2
没问题 @ReikoDev。如果你感兴趣的话,我应该链接到我写的关于此的文章。https://www.wilsonwilson.dev/articles/css-style-blending-using-flutter/ - Wilson Wilson
再次感谢@Wilson!我正在使用画布进行实验,这对我很有帮助! 有一个问题,当您拥有自定义布局(例如在Illustrator上制作的),类似于https://ibb.co/WV3JxZd/,您使用哪种方法将其在Flutter上实现? - Reiko Dev
1
@ReikoDev 哇,好多自定义绘制啊。不过我认为使用Flutter构建所有东西并不现实或好的想法。对于像星星、窗帘、横幅和可能的容器之类的东西,最好使用原始设计文件。对于像进度条这样的反应组件,你可以放心地交给自定义绘制:D - Wilson Wilson
哦,我明白了!再次感谢你,威尔逊!! - Reiko Dev
非常好的答案!简洁、灵活且可直接使用。谢谢。 - Ashkan Sarlak
显示剩余2条评论

10

尝试使用ShaderMask
BlendModes

使用示例:

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: SafeArea(
          child: MyHomePage(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<ui.Image> loadUiImage(String imageAssetPath) async {
    final data = await rootBundle.load(imageAssetPath);
    final completer = Completer<ui.Image>();
    ui.decodeImageFromList(Uint8List.view(data.buffer), completer.complete);
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<ui.Image>(
        future: loadUiImage('assets/cara_img.png'),
        builder: (context, img) {
          return img.hasData
              ? ShaderMask(
                  blendMode: BlendMode.colorDodge,
                  shaderCallback: (bounds) => ImageShader(
                    img.data,
                    TileMode.clamp,
                    TileMode.clamp,
                    Matrix4.identity().storage,
                  ),
                  child: Image.asset('assets/code.png'),
                )
              : Container(
                  color: Colors.red,
                  height: 100,
                  width: 100,
                  child: Text('loading'),
                );
        });
  }
}

code.png:
code.png cara_img.png:
cara_img.png

结果:
这里输入图片描述 这里输入图片描述 这里输入图片描述


这是一个很棒的解决方案,但它有点笨重,而且你必须异步加载图像。为什么不使用RenderObjects呢? - Wilson Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接