Флаттер - Как смешать изображение с градиентным цветом?

Я пытаюсь повторить дизайн экрана входа в систему, который мой дизайнер сделал для приложения.

Фоновое изображение использует blendMode из softLight, суть в том, что цвет, с которым оно смешивается, является градиентным цветом. Во-вторых, на самом деле есть два слоя разных градиентов (один фиолетовый градиент, один синий градиент)

Исходное изображение:

Исходное изображение

Окончательное изображение градиента

Окончательное изображение градиента

Теперь я попытался с помощью colorBlendMode, например,

Image.asset(
      'assets/pioneer-party.jpg',
      fit: BoxFit.cover,
      color: Color(0xff0d69ff).withOpacity(1.0),
      colorBlendMode: BlendMode.softLight,
    ),

Проблема в том, что свойство color принимает только один цвет.

Я тогда попробовал BoxDecoration, например

DecoratedBox(
      decoration: new BoxDecoration(
        color: const Color(0xff7c94b6),
        image: new DecorationImage(
          fit: BoxFit.cover,
          colorFilter: new ColorFilter.mode(Colors.purple.withOpacity(1.0), BlendMode.softLight),
          image: new NetworkImage(
            'http://www.allwhitebackground.com/images/2/2582-190x190.jpg',
          ),
        ),
      ),
    ),

Что все еще оставляет меня с той же проблемой. Затем я попытался сложить каждый слой по отдельности, а затем поиграть с градиентами, чтобы он выглядел близко к дизайну, например

Image.asset(
      'assets/pioneer-party.jpg',
      fit: BoxFit.cover,
      color: Color(0xff0d69ff).withOpacity(1.0),
      colorBlendMode: BlendMode.softLight,
    ),
    DecoratedBox(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: FractionalOffset.topCenter,
          end: FractionalOffset.bottomCenter,
          colors: [
            Color(0xff0d69ff).withOpacity(0.0),
            Color(0xff0069ff).withOpacity(0.8),
          ],
        ),
      ),
    ),
    DecoratedBox(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: FractionalOffset.topLeft,
          end: FractionalOffset.bottomRight,
          colors: [
            Color(0xff692eff).withOpacity(0.8),
            Color(0xff642cf4).withOpacity(0.8),
            Color(0xff602ae9).withOpacity(0.8),
            Color(0xff5224c8).withOpacity(0.8),
            Color(0xff5e29e5).withOpacity(0.8),
          ],
        stops: [0.0,0.25,0.5,0.75,1.0]
        ),
      ),
    ),

Что дает мне несколько близко к тому, что я хочу, но не совсем то, что мне нужно.

Кто-нибудь знает способ достижения этого?

РЕДАКТИРОВАТЬ:

Я также думал о смешении двух изображений вместе, но не нашел способа сделать это, кроме использования непрозрачности или чего-то еще. В идеале хотелось бы, чтобы он отображался изначально, а не с использованием "хаков" для его достижения.

8 ответов

Используйте стек, чтобы получить этот эффект очень легко.

Stack(
         children: <Widget>[
            Container(decoration: BoxDecoration(
              color: Colors.transparent,
              image: DecorationImage(
              fit: BoxFit.fill,
              image: AssetImage('images/bg.jpg',),
              ),
            ),
           height: 350.0,
          ),
          Container(
            height: 350.0,
            decoration: BoxDecoration(
              color: Colors.white,
              gradient: LinearGradient(
              begin: FractionalOffset.topCenter,
              end: FractionalOffset.bottomCenter,
              colors: [
                Colors.grey.withOpacity(0.0),
                Colors.black,
              ],
            stops: [0.0,1.0]
              )
            ),
          ),

ура

Вы тоже можете попробовать это:

ColorFiltered(
  colorFilter: ColorFilter.mode(Colors.red.withOpacity(0.4), BlendMode.srcOver),
  child: YourWidget(),
) 

Мое решение заключалось в обертывании моего изображения с помощью shaderMask, и это пример, который сработал для меня

      ShaderMask(
  shaderCallback: (bounds) {
    return LinearGradient(
      colors: node.badge!.colorsArray,
    ).createShader(bounds);
  },
  child: Image.asset(
    Assets.main_logo,
    height: 27.0,
    fit: BoxFit.cover,
  ),
  blendMode: BlendMode.srcATop,
),

Если вы обратитесь к документации BlendMode https://docs.flutter.io/flutter/dart-ui/BlendMode-class.html , вы действительно сможете найти то, что вам нужно.занятой кот

Класс виджетов ShaderMask будет полезен

      ShaderMask(

      // gradient layer  ----------------------------
      shaderCallback: (bound) {
        return  LinearGradient(
      end: FractionalOffset.topCenter,
      begin: FractionalOffset.bottomCenter,
      colors: [
        Colors.black.withOpacity(0.99),
        Colors.black.withOpacity(0.7),
        Colors.transparent,
      ],
      stops: [
        0.0,
        0.3,
        0.45
      ]) 
      .createShader(bound);
      },

      blendMode: BlendMode.srcOver,


      // your widget  ------------------------
      child: Container(
        height: double.infinity,
        width: double.infinity,
        child:
        Image.asset("image.png")
      ),
    );
      Container(
                  height: 35.h,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(6)),
                      image: DecorationImage(
                        image: AssetImage('assets/images/event_detail.png'),
                        fit: BoxFit.fill,

                      )),
                  child: Column(...

введите описание изображения здесь

Цитата из блока Если вы оберните столбец контейнером, он будет выглядеть так

      Container(
                  height: 35.h,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(6)),
                      image: DecorationImage(
                        image: AssetImage('assets/images/event_detail.png'),
                        fit: BoxFit.fill,

                      )),
                  child: Container( //This is our new Container
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.bottomLeft,
                        end: Alignment.topRight,
                        colors: [
                          Colors.red.withOpacity(0.8),
                          Colors.red.withOpacity(0.1),
                        ],
                      )
                    ),
                    child: Column(...

введите описание изображения здесь

Редактировать

The const double scale = 2.04015;работает только при запуске Flutter в браузере. Для мобильных устройств используйте const double scale = 0;

=================================================

Опоздала на вечеринку на три с половиной года, но заметила, что ни один из ответов не уловил даже понимания, в чем заключается желаемый эффект. Итак, давайте сначала проанализируем желаемый результат, а затем закодируем его.

Карты градиента

Этот эффект известен как «Карта градиента», поскольку значения отдельных пикселей сопоставляются с градиентом на основе предопределенного набора цветов.

Давайте взглянем. Если вход представляет собой изображение в градациях серого, Color color1 = Colors.blue, а также Color color2 = Colors.purple, цвета отдельных пикселей должны быть преобразованы следующим образом:

Где

  • Черный цвет пикселя - трансформируется в синий цвет
  • Белый цвет пикселей - преобразован в фиолетовый цвет
  • Промежуточный цвет — трансформируется в соответствующий цвет между синим и фиолетовым.

Код

Моими первоначальными мыслями было взять изображение, декодировать его в Uint8Listс использованием getBytes(), обработайте список и, наконец, закодируйте его обратно в png. Хотя этот подход работал, он был далеко не эффективен для сценария использования в реальном времени.

Поэтому вторым жизнеспособным подходом было создание эффекта с помощью цветовых матриц —

      class GradientMap extends StatelessWidget {

  final Color color1;
  final Color color2;
  final double contrast;
  final ImageProvider imageProvider;

  const GradientMap({
    this.color1 = Colors.white,
    this.color2 = Colors.black,
    this.contrast = 0,
    required this.imageProvider,
    Key? key
  }) : super(key: key);


  ColorFilter grayscaleMatrix() => ColorFilter.matrix([
    0.2126,       0.7152,       0.0722,       0, 0,
    0.2126,       0.7152,       0.0722,       0, 0,
    0.2126,       0.7152,       0.0722,       0, 0,
    contrast-0.4, contrast-0.4, contrast-0.4, 1, 0,
  ]);


  ColorFilter invertMatrix() => const ColorFilter.matrix([
    -1,  0,  0, 0, 255,
     0, -1,  0, 0, 255,
     0,  0, -1, 0, 255,
     0,  0,  0, 1, 0,
  ]);


  ColorFilter tintMatrix () {
    final int r = color2.red;
    final int g = color2.green;
    final int b = color2.blue;

    final double rTint = r / 255;
    final double gTint = g / 255;
    final double bTint = b / 255;

    const double scale = 2.04015; // Use 0 on mobile instead
    const double translate = 1 - scale * 0.5;

    return ColorFilter.matrix(<double>[
      (rTint * scale), (rTint * scale), (rTint * scale), (0), (r * translate),
      (gTint * scale), (gTint * scale), (gTint * scale), (0), (g * translate),
      (bTint * scale), (bTint * scale), (bTint * scale), (0), (b * translate),
      (0            ), (0            ), (0            ), (1), (0            ),
    ]);
  }


  @override
  Widget build(BuildContext context) {
    return Container(
      color: color1,
      child: ColorFiltered(
        colorFilter: tintMatrix(),
        child: ColorFiltered(
          colorFilter: invertMatrix(),
          child: ColorFiltered(
            colorFilter: grayscaleMatrix(),
            child: Image(image: imageProvider)),
        ),
      ),
    );
  }
}

В качестве альтернативы вы можете комбинировать grayscaleMatrix()а также invertMatrix()в один, чтобы исключить необходимость в третьем ColorFiltered()виджет.

Виджет принимает на вход

  • color1- зажигалка
  • color2- темнее Color
  • contrast- doubleсмешивание между цветом1 и цветом2
  • imageProvider- ImageProvider, например AssetImage()или же NetworkImage()

и отображает преобразованное изображение.

Применение

Просто оберните свой ImageProvider этим виджетом следующим образом:

      GradientMap(
  color1: Color.fromRGBO(158, 80, 254, 1),
  color2: Color.fromRGBO(15, 4, 192, 1),
  contrast: 0,
  imageProvider: NetworkImage("https://i.imgur.com/C5xknx8.png"),
)

Недостатки

Хотя этот подход эффективен, пользователь не может контролировать алгоритм смешивания двух входных цветов. Кроме того, виджет принимает на вход только два цвета. Это исключает возможность использования многоцветных градиентов.

         return Scaffold(
      body: Container(
        width: double.infinity,
        decoration: BoxDecoration(
            image: DecorationImage(
                fit: BoxFit.cover,
                image: AssetImage('assets/images/example.png')
            )
        ),
        child: OverflowBox(
          child: Container(
            decoration: BoxDecoration(
              gradient: LinearGradient(
                  begin: begin,
                  end: end,
                  colors: [
                    Color(0xFF000000),
                    Color(0x2d2d2d00)
                  ]
              ),
            ),
          ),
        ),
      ),
    );

вы можете обернуть контейнер внутри контейнера. Этот код работает для меня:

      Container(
      decoration: const BoxDecoration(
        image: DecorationImage(
            fit: BoxFit.cover,
            image: AssetImage('assets/images/background.jpg')),
      ),
      child: Container(
        padding: const EdgeInsets.fromLTRB(12, 40, 12, 0),
        width: double.infinity,
        decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [Colors.black.withOpacity(0), Colors.black]),
        ),
        child: <YOUR WIDGET>
Другие вопросы по тегам