Flutter实现动态高斯模糊

一个App加上高斯模糊会形成一种高级的感觉,本文将介绍如何制作一个根据背景内容来动态高斯模糊,效果如下

动态高斯模糊AppBar

实现思路

AppBar本质上是一个Widget,我们不直接通过ScaffoldappBar属性来设置AppBar,而是将我们需要的AppBar放在body属性里面,至于为什么不能放在appBar属性里面呢,最主要的原因是,ScaffoldappBar属性是顶级组件,在appBar以外的任何组件都无法覆盖或置于其下,而我们的主要实现思路是通过Stack组件的堆叠以及Positioned的位置设置来实现此效果

实现步骤

创建一个工程

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
    );
  }
}

创建自定义嵌套导航路由

App结构构建


为了使代码更好的管理和维护,我们对页面使用三层架构模式,即分为statelogicview三层,state层专注于数据,logic专注于页面的逻辑、view专注于页面的显示,这类似于Vue<template> <script> <style>分层模式


dependencies:
  flutter:
    sdk: flutter
  # 路由管理
  get: ^4.6.5
  # 屏幕适配工具
  flutter_screenutil: ^5.9.0

main.dart中更改为如下内容

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:text/router/routeconfig.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(720, 1080),
      minTextAdapt: true,
      splitScreenMode: true,
      builder: (context, child) {
        return GetMaterialApp(
          title: 'Flutter Demo',
          // 隐藏debug
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueGrey),
            useMaterial3: true,
          ),
          getPages: RouteConfig.getPages,
          // 初始化主页
          initialRoute: RouteConfig.main,
        );
      },
    );
  }
}

在lib中创建如下文件夹及文件
在这里插入图片描述

router文件夹的内容

routeconfig.dart

import 'package:get/get.dart';
import 'package:text/pages/appmain/view.dart';

class RouteConfig {
  //主页面
  static const String main = "/";

  static final List<GetPage> getPages = [
    GetPage(name: main, page: () => const AppMainPage()),
  ];
}
appmain文件夹的内容

state.dart

class AppMainState {
  AppMainState();
}

logic.dart


import 'package:get/get.dart';
import 'state.dart';

class AppMainLogic extends GetxController {
  final AppMainState state = AppMainState();
}

view.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'logic.dart';

class AppMainPage extends StatefulWidget {
  const AppMainPage({super.key});

  
  State<AppMainPage> createState() => _AppMainPageState();
}

class _AppMainPageState extends State<AppMainPage>
    with TickerProviderStateMixin {
  final logic = Get.put(AppMainLogic());
  final state = Get.find<AppMainLogic>().state;

  
  void initState() {
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
    );
  }
}

mainrouteconfig.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:text/pages/home/view.dart';

class NestedController extends GetxController {
  static const String home = "/homePage";
  static const String explore = "/explorePage";
  static const String musicLibrary = "/musicLibraryPage";

  static final Map<String, Widget> pages = {
    home: const HomePage(),
  };

  Route? onGenerateRoute(RouteSettings settings) {
    GetPageRoute? getPageRoute;
    pages.forEach((key, value) {
      if (settings.name == key) {
        getPageRoute = GetPageRoute(
          settings: settings,
          page: () => value,
          transition: Transition.fade,
        );
      }
    });

    return getPageRoute;
  }
}
home文件夹的内容

state.dart

class HomeState {
  HomeState() {
    ///Initialize variables
  }
}

logic.dart

import 'package:get/get.dart';

import 'state.dart';

class HomeLogic extends GetxController {
  final HomeState state = HomeState();
}

view.dart

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

import 'logic.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final logic = Get.put(HomeLogic());
  final state = Get.find<HomeLogic>().state;

  
  Widget build(BuildContext context) {
    return Container();
  }
}

创建完基础App架构模式后,我们开始自定义一个桥套导航,以便使我们在同一个页面内做页面的跳转,这类似于TabBar的效果

请添加图片描述

接下来我们就可以使用我们的自定义嵌套导航了, 页面的嵌套导航总管理在pages/appmain文件夹下的mainrouteconfig.dart,然后我们接着完成嵌套导航结构

自定义嵌套导航

在需要用到嵌套导航的页面引入如下代码

Navigator(
                      key: Get.nestedKey(1),
                      initialRoute: NestedController.home,
                      onGenerateRoute: state.nestedController.onGenerateRoute,
                    )

如我们想在appmain下的view.dart中引入,首先state层建立我们需要的数据,这里我使用了一个第三方的TabBar组件,大家想用官方的组件也可以,只需稍做修改即可

导入第三方组件依赖

dependencies:
  flutter:
    sdk: flutter
  # 路由管理
  get: ^4.6.5
  # 屏幕适配工具
  flutter_screenutil: ^5.9.0
  # bruno ui组件
  bruno: ^3.4.3

appmain/state.dart中修改如下

import 'package:bruno/bruno.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:text/pages/appmain/mainrouteconfig.dart';

class AppMainState {
  late List<BadgeTab> tabs; // Tab列表
  late TabController tabController; // Tab控制器

  late NestedController nestedController;

  AppMainState() {
    // 添加顶部tab
    tabs = [];
    tabs.add(BadgeTab(text: "首页"));
    tabs.add(BadgeTab(text: "发现"));
    tabs.add(BadgeTab(text: "乐库"));

    nestedController =
        Get.put(NestedController(), permanent: true); // 创建嵌套导航控制器
  }
}

appmain/logic.dart修改如下,这里的void brnTabBarOnTap(brnState, index)函数中多了两个页面,大家可以自行在pages创建另外两个页面,也可以根据自己的需求进行更改

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:text/pages/appmain/mainrouteconfig.dart';
import 'state.dart';

class AppMainLogic extends GetxController {
  final AppMainState state = AppMainState();

    void tabControllerInit(TickerProvider tickerProvider) {
    state.tabController =
        TabController(length: state.tabs.length, vsync: tickerProvider);
  }

  // 路由跳转控制
  void brnTabBarOnTap(brnState, index) {
    brnState.refreshBadgeState(index);
    switch (index) {
      case 0:
        Get.toNamed(NestedController.home, id: 1, arguments: {});
      case 1:
        Get.toNamed(NestedController.explore, id: 1, arguments: {});
      case 2:
        Get.toNamed(NestedController.musicLibrary, id: 1, arguments: {});
    }
  }
}

我创建的页面如下,内容和home文件夹下的各文件类似,只是类名不同

在这里插入图片描述

然后在appmain/mainrouteconfig.dart中稍作修改

  static const String home = "/homePage";
  static const String explore = "/explorePage";
  static const String musicLibrary = "/musicLibraryPage";
 
  static final Map<String, Widget> pages = {
    home: const HomePage(),
    explore: const ExplorePage(),
    musicLibrary: const MusicLibraryPage(),
  };

然后我们就可以在appmain/view中使用了,为了更好的适配多分辨率屏幕的影响,我们可以先定义一个页面缩放小工具,我们在lib目录下创建一个common文件夹,文件夹下创建一个utils文件夹,里面有个工具类screenadaptor.dart


在这里插入图片描述
工具类内容如下,此工具类是为了更好的管理横竖屏的像素而建立的类

import 'package:flutter_screenutil/flutter_screenutil.dart';

class ScreenAdaptor {

  ScreenAdaptor();
  double getLengthByOrientation(double horizon, double vertical)
  {
    return ScreenUtil().orientation.index == 0 ? horizon : vertical;
  }
}

final ScreenAdaptor screenAdaptor = ScreenAdaptor();

这时,我们就可以在appmain/view.dart中添加嵌套导航的组件以及一些样式的优化,代码如下

import 'package:bruno/bruno.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:text/common/utils/screenadaptor.dart';
import 'logic.dart';

class AppMainPage extends StatefulWidget {
  const AppMainPage({super.key});

  
  State<AppMainPage> createState() => _AppMainPageState();
}

class _AppMainPageState extends State<AppMainPage>
    with TickerProviderStateMixin {
  final logic = Get.put(AppMainLogic());
  final state = Get.find<AppMainLogic>().state;

  
  void initState() {
    super.initState();
    logic.tabControllerInit(this);
  }

    
  void dispose() {
    super.dispose();
    state.tabController.dispose();
    state.nestedController.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: ConstrainedBox(
        constraints: const BoxConstraints.expand(),
        child: Stack(
          children: [
            Positioned(
              // 不可以修改
              top: screenAdaptor.getLengthByOrientation(35.h, 35.h),
              child: SizedBox(
                width: ScreenUtil().screenWidth,
                height: ScreenUtil().screenHeight,
                child: Navigator(
                  key: Get.nestedKey(1),
                  initialRoute: "/homePage",
                  onGenerateRoute: state.nestedController.onGenerateRoute,
                ),
              ),
            ),
            Positioned(
              top: screenAdaptor.getLengthByOrientation(-10.h, -10.h),
              child: SizedBox(
                width: ScreenUtil().screenWidth,
                child: BrnAppBar(
                  // 状态栏和底部栏样式
                  systemOverlayStyle: const SystemUiOverlayStyle(
                    statusBarColor: Colors.transparent,
                    statusBarIconBrightness: Brightness.dark,
                    statusBarBrightness: Brightness.dark,
                    systemNavigationBarColor: Colors.transparent,
                  ),
                  primary: true,
                  // 不显示底部分割线
                  showDefaultBottom: false,
                  backgroundColor: Colors.transparent,
                  title: BrnTabBar(
                    indicatorColor: Colors.transparent,
                    backgroundcolor: Colors.transparent,
                    padding: EdgeInsets.fromLTRB(
                        screenAdaptor.getLengthByOrientation(70.w, 160.w),
                        0,
                        screenAdaptor.getLengthByOrientation(70.w, 160.w),
                        0),
                    mode: BrnTabBarBadgeMode.origin,
                    controller: state.tabController,
                    tabs: state.tabs,
                    onTap: logic.brnTabBarOnTap,
                    labelStyle: const TextStyle(
                      backgroundColor: Colors.transparent,
                      fontWeight: FontWeight.bold,
                    ),
                    unselectedLabelStyle: const TextStyle(
                      backgroundColor: Colors.transparent,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  leadingWidth:
                      screenAdaptor.getLengthByOrientation(100.w, 50.w),
                  leading: IconButton(
                    onPressed: (){}, // 打开侧边栏
                    icon: const Icon(Icons.menu),
                  ),
                  themeData: BrnAppBarConfig(
                    itemSpacing: 0,
                    leftAndRightPadding: 0,
                  ),
                  actions: <Widget>[
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(Icons.search),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

然后一运行,就可以发现大体效果出来了请添加图片描述
有了AppBar框架后,我们就可以实现我们的动态高斯模糊了

高斯模糊

组件的背景高斯模糊我们可以通过BackdropFilter这一个类来做实现,基于此,我封装了一个类来便于我们使用

我们在lib目录下新建一个component文件夹,里面创建一个BlurRectWidget的类
在这里插入图片描述

内容为

import 'dart:ui';

import 'package:flutter/material.dart';

class BlurRectWidget extends StatelessWidget {
  final Widget _widget;
  final double singmaX;
  final double singmaY;

  const BlurRectWidget(this._widget, {super.key, required this.singmaX, required this.singmaY});

  
  Widget build(BuildContext context) {
    return ClipRRect(
      child: BackdropFilter(
        filter: ImageFilter.blur(
          sigmaX: singmaX,
          sigmaY: singmaY,
        ),
        child: _widget,
      ),
    );
  }
}

使用方法

BlurRectWidget(
                  singmaX: 20,
                  singmaY: 20,
                  widget // 这里换成你想包裹的widget
               )

比如我们在appmain/view.dart中将BrnAppBar包裹起来

BlurRectWidget(
                  singmaX: 20,
                  singmaY: 20,
                  BrnAppBar( ... )   
                ),

运行查看效果

在这里插入图片描述

可以看到已有高斯模糊效果,那是不是动态的呢,我们在home/view里面添加一些内容来验证

home/view.dart内容修改如下

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:text/common/utils/screenadaptor.dart';

import 'logic.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final logic = Get.put(HomeLogic());
  final state = Get.find<HomeLogic>().state;

  
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.fromLTRB(
        screenAdaptor.getLengthByOrientation(24.h, 20.h),
        0,
        screenAdaptor.getLengthByOrientation(24.h, 20.h),
        0,
      ),
      child: Stack(
        children: [
          SizedBox(
            // 占满屏幕
            width: ScreenUtil().screenWidth,
            height: ScreenUtil().screenHeight,
            child: ListView(
              children: [
                // 占位
                SizedBox(
                  height: screenAdaptor.getLengthByOrientation(45.h, 45.h),
                ),
                Container(
                  color: Colors.blue,
                  height: 1000,
                  width: 100,
                  child: Image.network(
                    "https://t.mwm.moe/mp/",
                    fit: BoxFit.cover,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

重新运行效果
请添加图片描述


可以看到已经有了动态高斯模糊
自此高级观感的AppBar已经实现完毕

封装BackdropFilter

为了能像使用CSS里的filter一样方便,我封装了一个BackdropCSSFilter类,
此类基于https://github.com/iofod/flutter_css_filter做修改,因为原作者的CSSFilter对组件进行模糊而不是对背景进行模糊,所以我做修改了

common/utils目录下创建如下文件

在这里插入图片描述

base.dart内容如下

import 'dart:math' as math;
import 'css_filter.dart';
import 'utils.dart';

class BackdropFilterMatrix {
  /// Check: https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/contrast()
  static contrast({required List<double> matrix, required double value}) {
    double v = value;
    double b = (1.0 - value) * 0.5 * 255.0; // 0.5*255 => 127

    return multiplyMatrix5(matrix, <double>[
      v,
      0,
      0,
      0,
      b,
      0,
      v,
      0,
      0,
      b,
      0,
      0,
      v,
      0,
      b,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  /// Formula from: https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent
  static grayscale({required List<double> matrix, required double value}) {
    double v = 1.0 - value;
    double lumR = 0.2126;
    double lumG = 0.7152;
    double lumB = 0.0722;

    return multiplyMatrix5(matrix, <double>[
      (lumR + (1 - lumR) * v),
      (lumG - lumG * v),
      (lumB - lumB * v),
      0,
      0,
      (lumR - lumR * v),
      (lumG + (1 - lumG) * v),
      (lumB - lumB * v),
      0,
      0,
      (lumR - lumR * v),
      (lumG - lumG * v),
      (lumB + (1 - lumB) * v),
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  /// Formula from: https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent
  static sepia({required List<double> matrix, required double value}) {
    double v = 1.0 - value;

    return multiplyMatrix5(matrix, <double>[
      (0.393 + 0.607 * v),
      (0.769 - 0.769 * v),
      (0.189 - 0.189 * v),
      0,
      0,
      (0.349 - 0.349 * v),
      (0.686 + 0.314 * v),
      (0.168 - 0.168 * v),
      0,
      0,
      (0.272 - 0.272 * v),
      (0.534 - 0.534 * v),
      (0.131 + 0.869 * v),
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  /// Check: https://www.geeksforgeeks.org/css-invert-function/
  static invert({required List<double> matrix, required double value}) {
    // v * (255 - n) + (1 - v) * n => (1 - 2v) * n + 255 * v
    double v = value * 255.0;
    double k = 1.0 - 2.0 * value;

    // The fifth column n is 255.
    return multiplyMatrix5(matrix, <double>[
      k,
      0,
      0,
      0,
      v,
      0,
      k,
      0,
      0,
      v,
      0,
      0,
      k,
      0,
      v,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  /// Check: https://stackoverflow.com/questions/64639589/how-to-adjust-hue-saturation-and-brightness-of-an-image-in-flutter
  static hue({required List<double> matrix, required double value}) {
    double v = math.pi * (value / 180.0);
    double cosVal = math.cos(v);
    double sinVal = math.sin(v);
    double lumR = 0.213;
    double lumG = 0.715;
    double lumB = 0.072;

    return multiplyMatrix5(matrix, <double>[
      (lumR + (cosVal * (1 - lumR))) + (sinVal * (-lumR)),
      (lumG + (cosVal * (-lumG))) + (sinVal * (-lumG)),
      (lumB + (cosVal * (-lumB))) + (sinVal * (1 - lumB)),
      0,
      0,
      (lumR + (cosVal * (-lumR))) + (sinVal * 0.143),
      (lumG + (cosVal * (1 - lumG))) + (sinVal * 0.14),
      (lumB + (cosVal * (-lumB))) + (sinVal * (-0.283)),
      0,
      0,
      (lumR + (cosVal * (-lumR))) + (sinVal * (-(1 - lumR))),
      (lumG + (cosVal * (-lumG))) + (sinVal * lumG),
      (lumB + (cosVal * (1 - lumB))) + (sinVal * lumB),
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  static brightness({required List<double> matrix, required double value}) {
    double v = value;

    return multiplyMatrix5(matrix, <double>[
      v,
      0,
      0,
      0,
      0,
      0,
      v,
      0,
      0,
      0,
      0,
      0,
      v,
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }

  /// Check: https://docs.rainmeter.net/tips/colormatrix-guide/
  static saturate({required List<double> matrix, required double value}) {
    return BackdropFilterMatrix.grayscale(matrix: matrix, value: 1.0 - value);
  }

  static opacity({required List<double> matrix, required double value}) {
    return multiplyMatrix5(matrix, <double>[
      1,
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      1,
      0,
      0,
      0,
      0,
      0,
      value,
      0,
      0,
      0,
      0,
      0,
      1
    ]);
  }
}

// Allows matrix multiplication.
final filterTypeMap = {
  'contrast': BackdropFilterMatrix.contrast,
  'grayscale': BackdropFilterMatrix.grayscale,
  'hueRotate': BackdropFilterMatrix.hue,
  'brightness': BackdropFilterMatrix.brightness,
  'saturate': BackdropFilterMatrix.saturate,
  'opacity': BackdropFilterMatrix.opacity,
};

// Not superimposed on the original matrix.
final filterAloneMap = {
  'sepia': BackdropCSSFilter.sepia,
  'invert': BackdropCSSFilter.invert,
  'blur': BackdropCSSFilter.blur
};

css_filter.dart

/// CSS filter for flutter
/// Author: qkorbit
/// Released under BSD-3-Clause License.
library css_filter;

export 'filter.dart';
export 'presets.dart';
export 'base.dart' show BackdropFilterMatrix;
export 'utils.dart' show BackdropCSSFilterMatrix;

filter.dart

import 'dart:ui';
import 'package:flutter/material.dart';
import 'utils.dart';
import 'base.dart';

/// Use CSS filter effects on flutter's Widget. All CSS filters are implemented except `drop-shadow()`.
/// `drop-shadow()` should be replaced by the [BoxShadow](https://api.flutter.dev/flutter/painting/BoxShadow-class.html) or [Shadow](https://api.flutter.dev/flutter/dart-ui/Shadow-class.html) widget.
///
/// Example:
///
/// ```dart
/// CSSFilter.contrast(child: const Text('foo'), value: 1.2);
/// ```
///
/// Support effects:
/// * contrast()
/// * grayscale()
/// * sepia()
/// * hueRotate()
/// * brightness()
/// * saturate()
/// * invert()
/// * blur()
/// * opacity()
class BackdropCSSFilter {
  /// Adjusts the contrast of the input widget.
  /// A value under 1.0 decreases the contrast, while a value over 1.0 increases it.
  /// A value of 0.0 will make it completely gray.
  /// Default value is 1.0.
  static Widget contrast({required Widget child, required double value}) {
    if (!isNotDefault(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.contrast(matrix: baseMatrix(), value: value),
        child);
  }

  /// Converts the input widget to grayscale.
  /// Values between 0.0 and 1.0 are linear multipliers on the effect.
  /// A value of 1.0 is completely grayscale.
  /// Default value is 0.0.
  static Widget grayscale({required Widget child, required double value}) {
    if (!isNotNegative(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.grayscale(matrix: baseMatrix(), value: value),
        child);
  }

  /// Converts the input widget to sepia, giving it a warmer, more yellow/brown appearance.
  /// Values between 0.0 and 1.0 are linear multipliers on the effect.
  /// A value of 1.0 is completely sepia.
  /// Default value is 0.0.
  static Widget sepia({required Widget child, required double value}) {
    if (!isNotNegative(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.sepia(matrix: baseMatrix(), value: value), child);
  }

  /// Rotates the [hue](https://en.wikipedia.org/wiki/Hue) of the input widget.
  /// A positive hue rotation increases the hue value, while a negative rotation decreases the hue value.
  /// @parmas value: A value of rotate angle.
  /// Default value is 0.0.
  static Widget hueRotate({required Widget child, required double value}) {
    if (value == 0.0) return child;

    return execFilterSample(
        BackdropFilterMatrix.hue(matrix: baseMatrix(), value: value), child);
  }

  /// Apply a linear multiplier to the input widget, making it appear brighter or darker.
  /// A value under 1.0 darkens the Widget, while a value over 1.0 brightens it.
  /// A value of 0.0 will make it completely black.
  /// Default value is 1.0.
  static Widget brightness({required Widget child, required double value}) {
    if (!isNotDefault(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.brightness(matrix: baseMatrix(), value: value),
        child);
  }

  /// Super-saturates or desaturates the input widget.
  /// A value under 1.0 desaturates the Widget, while a value over 1.0 super-saturates it.
  /// A value of 0.0 is completely unsaturated.
  /// Default value is 1.0.
  static Widget saturate({required Widget child, required double value}) {
    if (!isNotDefault(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.saturate(matrix: baseMatrix(), value: value),
        child);
  }

  /// Inverts the color of input widget.
  /// Values between 0.0 and 1.0 are linear multipliers on the effect.
  /// A value of 1.0 is completely inverted.
  /// Default value is 0.0.
  static Widget invert({required Widget child, required double value}) {
    if (!isNotNegative(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.invert(matrix: baseMatrix(), value: value), child);
  }

  /// Apply a Gaussian blur to the input widget.
  /// A larger value will create more blur on input widget.
  /// @parmas value: A value of blur radius.
  /// Default value is 0.0.
  static Widget blur({required Widget child, required double value}) {
    if (!isNotNegative(value)) return child;

    return ClipRRect(
      child: BackdropFilter(
          filter: ImageFilter.blur(
              sigmaX: value, sigmaY: value, tileMode: TileMode.decal),
          child: child),
    );
  }

  /// Apply transparency to input widget.
  /// Values between 0.0 and 1.0 are linear multipliers on the effect.
  /// A value of 0.0 is completely transparent.
  /// Default value is 1.0.
  static Widget opacity({required Widget child, required double value}) {
    if (!isNotDefault(value)) return child;

    return execFilterSample(
        BackdropFilterMatrix.opacity(matrix: baseMatrix(), value: value),
        child);
  }

  /// A quick and efficient way to apply multiple filters to the input widget.
  /// You can use any combination of these filter effects.
  ///
  /// Example:
  ///
  /// ```dart
  /// CSSFilter.apply(child: const Text('Hello World!'), value: CSSFilterMatrix().contrast(0.5).blur(3.0));
  /// CSSFilter.apply(child: const Text('Hello World!'), value: CSSFilterMatrix().brightness(1.2).saturate(1.5));
  /// ```
  ///
  static Widget apply(
      {required Widget child, required BackdropCSSFilterMatrix value}) {
    List<double> matrix = baseMatrix();
    Widget tree = child;
    bool canMerge = false;

    value.conf.forEach((K, V) {
      var fn = filterTypeMap[K];

      if (fn != null) {
        matrix = fn(matrix: matrix, value: V);
        canMerge = true;
      } else {
        // merge layers once
        if (canMerge) {
          tree = ClipRRect(
            child: BackdropFilter(
              filter: toColorFilterMatrix(matrix),
              child: tree,
            ),
          );

          canMerge = false;
        }

        var alone = filterAloneMap[K];

        tree = alone!(child: tree, value: V);
        // reset matrix
        matrix = baseMatrix();
      }
    });

    if (!canMerge) return tree;

    return ClipRRect(
      child: BackdropFilter(
        filter: toColorFilterMatrix(matrix),
        child: tree,
      ),
    );
  }
}

presets.dart

import 'package:flutter/material.dart';

import 'filter.dart';
import 'utils.dart';

/// Added more preset filter effects to CSSFilter.
/// The current version adds instagram filter package, the values mainly refer to [CSSgram](https://github.com/una/CSSgram), partly refer to [instagram.css](https://github.com/picturepan2/instagram.css).
///
/// Example:
///
/// ```dart
/// CSSFilterPresets.insAshby(child: const Text('foo'));
/// CSSFilterPresets.insHelena(child: const Text('bar'));
/// ```
///
/// Support effects:
/// * ins1977()
/// * ins1977V2()
/// * insAden()
/// * insAmaro()
/// * insAshby()
/// * insBrannan()
/// * insBrooklyn()
/// * insClarendon()
/// * insDogpatch()
/// * insEarlybird()
/// * insGingham()
/// * insHelena()
/// * insHudson()
/// * insInkwell()
/// * insInkwellV2()
/// * insJuno()
/// * insKelvin()
/// * insLark()
/// * insLofi()
/// * insLudwig()
/// * insMaven()
/// * insMayfair()
/// * insMoon()
/// * insMoonV2()
/// * insNashville()
/// * insNashvilleV2()
/// * insPerpetua()
/// * insPoprocket()
/// * insReyes()
/// * insRise()
/// * insSierra()
/// * insSkyline()
/// * insSlumber()
/// * insStinson()
/// * insSutro()
/// * insToaster()
/// * insToasterV2()
/// * insValencia()
/// * insVesper()
/// * insWalden()
/// * insWaldenV2()
/// * insWillow()
/// * insXpro2()
///
class CSSFilterPresets {
  /// A quick and efficient way to apply preset effects to the input widget.
  /// You can even adjust the intensity of the preset effects.
  ///
  /// Example:
  ///
  /// ```dart
  /// CSSFilterPresets.apply(
  ///   child: const Text('foo'),
  ///   value: CSSFilterPresets.insMaven,
  ///   strength: 0.6
  /// );
  /// ```
  ///
  /// If the input widget is transparent, then the `alphaBlending` parameter should be set to adjust the [Alpha Compositing](https://ciechanow.ski/alpha-compositing/) to avoid gross overlay of transparency.
  /// In general, `alphaBlending` is set to the same opacity value as the input widget. If the opacity of the input widget is unknown, the `alphaBlending` value is set according to the actual situation.
  ///
  static Widget apply(
      {required Widget child,
      required Function value,
      double strength = 1.0,
      double alphaBlending = 1.0}) {
    if (strength <= 0.0) return child;
    if (strength >= 1.0) strength = 1.0;

    Widget filtered = value(child: child);

    if (strength == 1.0) return filtered;
    if (alphaBlending > 1.0) alphaBlending = 1.0;
    if (alphaBlending < 0.0) alphaBlending = 0.0;

    Widget tree = Stack(children: [
      Positioned(child: child),
      Positioned(
          child:
              IgnorePointer(child: Opacity(opacity: strength, child: filtered)))
    ]);

    if (alphaBlending == 1.0) return tree;

    return Opacity(
        opacity: 1.0 - (1.0 - alphaBlending) * strength, child: tree);
  }

  static Widget origin({required Widget child}) {
    return child;
  }

  static Widget ins1977({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(243, 106, 188, 0.3)),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().contrast(1.1).brightness(1.1).saturate(1.3)),
    );
  }

  static Widget ins1977V2({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix().sepia(0.5).hueRotate(-30.0).saturate(1.4));
  }

  static Widget insAden({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderLinearSample([
        const Color.fromRGBO(66, 10, 14, 0.2),
        const Color.fromRGBO(0, 0, 0, 0.0)
      ], Alignment.centerRight),
      blendMode: BlendMode.darken,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .hueRotate(-20.0)
              .contrast(0.9)
              .saturate(0.85)
              .brightness(1.2)),
    );
  }

  static Widget insAmaro({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(125, 105, 24, 0.2)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.35)
              .contrast(1.1)
              .brightness(1.2)
              .saturate(1.3)),
    );
  }

  static Widget insAshby({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(125, 105, 24, 0.35)),
      blendMode: BlendMode.lighten,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().sepia(0.5).contrast(1.2).saturate(1.8)),
    );
  }

  static Widget insBrannan({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(161, 44, 199, 0.31)),
      blendMode: BlendMode.lighten,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().sepia(0.5).contrast(1.4)),
    );
  }

  static Widget insBrooklyn({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(168, 223, 193, 0.4),
        const Color.fromRGBO(196, 183, 200, 1.0)
      ], [
        0.0,
        0.7
      ]),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().contrast(0.9).brightness(1.1)),
    );
  }

  static Widget insClarendon({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(127, 187, 227, 0.2)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().contrast(1.2).saturate(1.35)),
    );
  }

  static Widget insDogpatch({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix().sepia(0.35).saturate(1.1).contrast(1.5));
  }

  static Widget insEarlybird({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(208, 186, 142, 1.0),
        const Color.fromRGBO(54, 3, 9, 1.0),
        const Color.fromRGBO(29, 2, 16, 1.0)
      ], [
        0.2,
        0.85,
        1.0
      ]),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().contrast(0.9).sepia(0.2)),
    );
  }

  static Widget insGingham({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(230, 230, 250, 1.0)),
      blendMode: BlendMode.softLight,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().brightness(1.05).hueRotate(-10.0)),
    );
  }

  static Widget insHelena({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(158, 175, 30, 0.25)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.5)
              .contrast(1.05)
              .brightness(1.05)
              .saturate(1.35)),
    );
  }

  static Widget insHudson({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(166, 177, 255, 0.5),
        const Color.fromRGBO(52, 33, 52, 0.5)
      ], [
        0.5,
        1.0
      ]),
      blendMode: BlendMode.multiply,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().brightness(1.2).contrast(0.9).saturate(1.1)),
    );
  }

  static Widget insInkwell({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .sepia(0.3)
            .contrast(1.1)
            .brightness(1.1)
            .grayscale(1.0));
  }

  static Widget insInkwellV2({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value:
        BackdropCSSFilterMatrix().brightness(1.25).contrast(0.85).grayscale(1.0));
  }

  static Widget insJuno({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(127, 187, 227, 0.2)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.35)
              .contrast(1.15)
              .brightness(1.15)
              .saturate(1.8)),
    );
  }

  static Widget insKelvin({required Widget child}) {
    Widget sub = ShaderMask(
        shaderCallback:
            execShaderDirectSample(const Color.fromRGBO(56, 44, 52, 1.0)),
        blendMode: BlendMode.colorDodge,
        child: child);

    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(183, 125, 33, 1.0)),
      blendMode: BlendMode.overlay,
      child: sub,
    );
  }

  static Widget insLark({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .sepia(0.25)
            .contrast(1.2)
            .brightness(1.3)
            .saturate(1.25));
  }

  static Widget insLofi({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child, value: BackdropCSSFilterMatrix().saturate(1.1).contrast(1.5));
  }

  static Widget insLudwig({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(125, 105, 24, 0.1)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.25)
              .contrast(1.05)
              .brightness(1.05)
              .saturate(2.0)),
    );
  }

  static Widget insMaven({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(3, 230, 26, 0.2)),
      blendMode: BlendMode.hue,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .contrast(0.95)
              .brightness(0.95)
              .saturate(1.5)
              .sepia(0.25)),
    );
  }

  static Widget insMayfair({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(255, 255, 255, 0.32),
        const Color.fromRGBO(255, 200, 200, 0.24),
        const Color.fromRGBO(17, 17, 17, 0.4)
      ], const [
        0.0,
        0.0,
        0.6
      ]),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().contrast(1.1).saturate(1.1)),
    );
  }

  static Widget insMoon({required Widget child}) {
    Widget sub = ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(160, 160, 160, 1.0)),
      blendMode: BlendMode.softLight,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().grayscale(1).contrast(1.1).brightness(1.1)),
    );

    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(56, 56, 56, 1.0)),
      blendMode: BlendMode.lighten,
      child: sub,
    );
  }

  static Widget insMoonV2({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .brightness(1.4)
            .contrast(0.95)
            .saturate(0.0)
            .sepia(0.35));
  }

  static Widget insNashville({required Widget child}) {
    Widget sub = ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(247, 176, 153, 0.56)),
      blendMode: BlendMode.darken,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.2)
              .contrast(1.2)
              .brightness(1.05)
              .saturate(1.2)),
    );

    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(0, 70, 150, 0.4)),
      blendMode: BlendMode.lighten,
      child: sub,
    );
  }

  static Widget insNashvilleV2({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(128, 78, 15, 0.5),
        const Color.fromRGBO(128, 78, 15, 0.65)
      ]),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.25)
              .contrast(1.5)
              .brightness(0.9)
              .hueRotate(-15.0)),
    );
  }

  static Widget insPerpetua({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderLinearSample([
        const Color.fromRGBO(0, 91, 154, 0.5),
        const Color.fromRGBO(230, 193, 61, 0.5)
      ], Alignment.bottomCenter),
      blendMode: BlendMode.softLight,
      child: child,
    );
  }

  static Widget insPoprocket({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(206, 39, 70, 0.75),
        const Color.fromRGBO(0, 0, 0, 1.0)
      ], const [
        0.4,
        0.8
      ]),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().sepia(0.15).brightness(1.2)),
    );
  }

  static Widget insReyes({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .sepia(0.75)
            .contrast(0.75)
            .brightness(1.25)
            .saturate(1.4));
  }

  static Widget insRise({required Widget child}) {
    Widget sub = ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(236, 205, 169, 0.15),
        const Color.fromRGBO(50, 30, 7, 0.4)
      ], [
        0.55,
        1.0
      ]),
      blendMode: BlendMode.multiply,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .brightness(1.05)
              .sepia(0.2)
              .contrast(0.9)
              .saturate(0.9)),
    );

    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(232, 197, 152, 0.48),
        const Color.fromRGBO(0, 0, 0, 0.0)
      ], [
        0.0,
        0.9
      ]),
      blendMode: BlendMode.overlay,
      child: sub,
    );
  }

  static Widget insSierra({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(128, 78, 15, 0.5),
        const Color.fromRGBO(0, 0, 0, 0.65)
      ]),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.25)
              .contrast(1.5)
              .brightness(0.9)
              .hueRotate(-15.0)),
    );
  }

  static Widget insSkyline({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .sepia(0.15)
            .contrast(1.25)
            .brightness(1.25)
            .saturate(1.2));
  }

  static Widget insSlumber({required Widget child}) {
    Widget sub = ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(69, 41, 12, 0.4)),
      blendMode: BlendMode.lighten,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().saturate(0.66).brightness(1.05)),
    );

    return ShaderMask(
        shaderCallback:
            execShaderDirectSample(const Color.fromRGBO(125, 105, 24, 0.5)),
        blendMode: BlendMode.softLight,
        child: sub);
  }

  static Widget insStinson({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(240, 149, 128, 0.2)),
      blendMode: BlendMode.softLight,
      child: BackdropCSSFilter.apply(
          child: child,
          value:
          BackdropCSSFilterMatrix().contrast(0.75).saturate(0.85).brightness(1.15)),
    );
  }

  static Widget insSutro({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(0, 0, 0, 0.0),
        const Color.fromRGBO(0, 0, 0, 0.5)
      ], const [
        0.5,
        0.9
      ]),
      blendMode: BlendMode.darken,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.4)
              .contrast(1.2)
              .brightness(0.9)
              .saturate(1.4)
              .hueRotate(-10.0)),
    );
  }

  static Widget insToaster({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(128, 78, 15, 1.0),
        const Color.fromRGBO(59, 0, 59, 1.0)
      ]),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child, value: BackdropCSSFilterMatrix().contrast(1.3).brightness(0.9)),
    );
  }

  static Widget insToasterV2({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(128, 78, 15, 1.0),
        const Color.fromRGBO(0, 0, 0, 0.25)
      ]),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.25)
              .contrast(1.5)
              .brightness(0.95)
              .hueRotate(-15.0)),
    );
  }

  static Widget insValencia({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(58, 3, 57, 0.5)),
      blendMode: BlendMode.exclusion,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix().contrast(1.08).brightness(1.08).sepia(0.08)),
    );
  }

  static Widget insVesper({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(125, 105, 24, 0.25)),
      blendMode: BlendMode.overlay,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.35)
              .contrast(1.15)
              .brightness(1.2)
              .saturate(1.3)),
    );
  }

  static Widget insWalden({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(0, 68, 204, 0.3)),
      blendMode: BlendMode.screen,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .brightness(1.1)
              .hueRotate(-10.0)
              .sepia(0.3)
              .saturate(1.6)),
    );
  }

  static Widget insWaldenV2({required Widget child}) {
    return ShaderMask(
      shaderCallback:
          execShaderDirectSample(const Color.fromRGBO(229, 240, 128, 0.5)),
      blendMode: BlendMode.darken,
      child: BackdropCSSFilter.apply(
          child: child,
          value: BackdropCSSFilterMatrix()
              .sepia(0.35)
              .contrast(0.8)
              .brightness(1.25)
              .saturate(1.4)),
    );
  }

  static Widget insWillow({required Widget child}) {
    return BackdropCSSFilter.apply(
        child: child,
        value: BackdropCSSFilterMatrix()
            .brightness(1.2)
            .contrast(0.85)
            .saturate(0.05)
            .sepia(0.2));
  }

  static Widget insXpro2({required Widget child}) {
    return ShaderMask(
      shaderCallback: execShaderRadialSample([
        const Color.fromRGBO(230, 231, 224, 1.0),
        const Color.fromRGBO(43, 42, 161, 0.6)
      ], [
        0.4,
        1.1
      ]),
      blendMode: BlendMode.colorBurn,
      child: BackdropCSSFilter.apply(child: child, value: BackdropCSSFilterMatrix().sepia(0.3)),
    );
  }
}

utils.dart

import 'package:flutter/material.dart';

List<double> baseMatrix() {
  return <double>[
    1,
    0,
    0,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    0,
    0,
    1
  ];
}

/// Check: https://github.com/openkraken/kraken/blob/main/kraken/lib/src/css/filter.dart
/// Calc 5x5 matrix multiplication.
List<double> multiplyMatrix5(List<double> a, List<double> b) {
  if (a.length != b.length) {
    throw FlutterError('Matrix length should be same.');
  }

  if (a.length != 25) {
    throw FlutterError('Matrix5 size is not correct.');
  }

  var a00 = a[0];
  var a01 = a[1];
  var a02 = a[2];
  var a03 = a[3];
  var a04 = a[4];
  var a10 = a[5];
  var a11 = a[6];
  var a12 = a[7];
  var a13 = a[8];
  var a14 = a[9];
  var a20 = a[10];
  var a21 = a[11];
  var a22 = a[12];
  var a23 = a[13];
  var a24 = a[14];
  var a30 = a[15];
  var a31 = a[16];
  var a32 = a[17];
  var a33 = a[18];
  var a34 = a[19];
  var a40 = a[20];
  var a41 = a[21];
  var a42 = a[22];
  var a43 = a[23];
  var a44 = a[24];

  var b00 = b[0];
  var b01 = b[1];
  var b02 = b[2];
  var b03 = b[3];
  var b04 = b[4];
  var b10 = b[5];
  var b11 = b[6];
  var b12 = b[7];
  var b13 = b[8];
  var b14 = b[9];
  var b20 = b[10];
  var b21 = b[11];
  var b22 = b[12];
  var b23 = b[13];
  var b24 = b[14];
  var b30 = b[15];
  var b31 = b[16];
  var b32 = b[17];
  var b33 = b[18];
  var b34 = b[19];
  var b40 = b[20];
  var b41 = b[21];
  var b42 = b[22];
  var b43 = b[23];
  var b44 = b[24];

  return [
    a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30 + a04 * b40,
    a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31 + a04 * b41,
    a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32 + a04 * b42,
    a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33 + a04 * b43,
    a00 * b04 + a01 * b14 + a02 * b24 + a03 * b34 + a04 * b44,
    a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30 + a14 * b40,
    a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41,
    a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42,
    a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43,
    a10 * b04 + a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44,
    a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30 + a24 * b40,
    a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41,
    a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42,
    a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43,
    a20 * b04 + a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44,
    a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30 + a34 * b40,
    a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41,
    a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42,
    a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43,
    a30 * b04 + a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44,
    a40 * b00 + a41 * b10 + a42 * b20 + a43 * b30 + a44 * b40,
    a40 * b01 + a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41,
    a40 * b02 + a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42,
    a40 * b03 + a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43,
    a40 * b04 + a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44,
  ];
}

ColorFilter toColorFilterMatrix(List<double> matrix) {
  return ColorFilter.matrix(matrix.sublist(0, 20));
}

Widget execFilterSample(List<double> matrix, Widget child) {
  return ClipRRect(
      child: BackdropFilter(filter: toColorFilterMatrix(matrix), child: child));
}

bool isNotNegative(double v) {
  return v > 0.0;
}

bool isNotDefault(double v) {
  return v != 1.0 && v >= 0.0;
}

execShaderDirectSample(Color color, [Alignment end = Alignment.centerRight]) {
  return (Rect bounds) {
    return LinearGradient(
        end: end,
        colors: [color, color],
        stops: const [0.0, 1.0]).createShader(bounds);
  };
}

execShaderLinearSample(List<Color> colors,
    [Alignment end = Alignment.centerRight,
    List<double> stops = const [0.0, 1.0]]) {
  return (Rect bounds) {
    return LinearGradient(end: end, colors: colors, stops: stops)
        .createShader(bounds);
  };
}

execShaderRadialSample(List<Color> colors,
    [List<double> stops = const [0.0, 1.0], radius = 0.8]) {
  return (Rect bounds) {
    return RadialGradient(
            center: Alignment.center,
            radius: radius,
            colors: colors,
            stops: stops
            // tileMode: TileMode.mirror,
            )
        .createShader(bounds);
  };
}

/// Generates the configuration for applying CSSFilter effects, which is provided to `CSSFilter.apply` for use.
/// Supports chain calls.
///
/// Example:
///
/// ```dart
/// CSSFilter.apply(
///   child: const Text('Hello World!'),
///   value: CSSFilterMatrix().contrast(1.5).sepia(0.4)
/// );
/// ```
class BackdropCSSFilterMatrix {
  Map conf = {};

  BackdropCSSFilterMatrix contrast([double value = 1.0]) {
    conf['contrast'] = value;
    return this;
  }

  BackdropCSSFilterMatrix grayscale([double value = 0.0]) {
    conf['grayscale'] = value;
    return this;
  }

  BackdropCSSFilterMatrix sepia([double value = 0.0]) {
    conf['sepia'] = value;
    return this;
  }

  BackdropCSSFilterMatrix hueRotate([double value = 0.0]) {
    conf['hueRotate'] = value;
    return this;
  }

  BackdropCSSFilterMatrix brightness([double value = 1.0]) {
    conf['brightness'] = value;
    return this;
  }

  BackdropCSSFilterMatrix saturate([double value = 1.0]) {
    conf['saturate'] = value;
    return this;
  }

  BackdropCSSFilterMatrix invert([double value = 0.0]) {
    conf['invert'] = value;
    return this;
  }

  BackdropCSSFilterMatrix blur([double value = 0.0]) {
    conf['blur'] = value;
    return this;
  }

  BackdropCSSFilterMatrix opacity([double value = 1.0]) {
    conf['opacity'] = value;
    return this;
  }
}

至此封装完毕使用方法

BackdropCSSFilter.blur(
                  value: 10,
                  child: BrnAppBar( ... )   
                ),

效果同理

在这里插入图片描述

去除底部小白条

至于如何去除底部小白条,可以参考我的一篇博客Flutter TabBar下方白条隐藏
去除白条后的效果

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter动态表单实现的一般步骤如下: 1. 定义表单字段数据模型:定义一个类来表示每个表单字段,包括字段类型、名称、值、是否必填等属性。 2. 构建表单UI:使用Flutter提供的表单控件,如TextFormField、Checkbox、Radio等来构建表单的UI。 3. 根据字段数据模型动态生成表单控件:根据表单字段数据模型动态生成相应的表单控件,可以使用Flutter的Widget库中的工厂方法来实现。 4. 收集表单数据:根据表单字段数据模型收集用户填写的表单数据,并进行校验处理。 5. 提交表单数据:将收集到的表单数据提交到服务器进行处理。 下面是一个简单的Flutter动态表单实现的示例代码: ```dart class FormField { final String label; final String type; final bool required; String value; FormField({ required this.label, required this.type, this.required = false, this.value = '', }); } class DynamicFormScreen extends StatefulWidget { @override _DynamicFormScreenState createState() => _DynamicFormScreenState(); } class _DynamicFormScreenState extends State<DynamicFormScreen> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); List<FormField> _fields = [ FormField(label: 'Name', type: 'text', required: true), FormField(label: 'Email', type: 'email', required: true), FormField(label: 'Phone', type: 'tel', required: true), FormField(label: 'Message', type: 'textarea', required: false), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Dynamic Form'), ), body: Form( key: _formKey, child: ListView.builder( itemCount: _fields.length, itemBuilder: (BuildContext context, int index) { FormField field = _fields[index]; Widget widget; switch (field.type) { case 'text': case 'email': case 'tel': widget = TextFormField( decoration: InputDecoration( labelText: field.label, ), keyboardType: TextInputType.text, validator: (value) { if (field.required && value!.isEmpty) { return 'This field is required'; } return null; }, onSaved: (value) { field.value = value!; }, ); break; case 'textarea': widget = TextFormField( decoration: InputDecoration( labelText: field.label, ), keyboardType: TextInputType.multiline, maxLines: 4, validator: (value) { if (field.required && value!.isEmpty) { return 'This field is required'; } return null; }, onSaved: (value) { field.value = value!; }, ); break; default: widget = Container(); } return widget; }, ), ), floatingActionButton: FloatingActionButton( onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); // Submit form data } }, child: Icon(Icons.send), ), ); } } ``` 在上面的示例代码中,我们定义了一个FormField类来表示表单字段,包括字段名称、类型、是否必填以及字段值等属性。然后我们在StatefulWidget的状态类中定义了一个_fields列表来存储表单字段数据模型。在build方法中,我们使用ListView.builder来构建表单UI,根据表单字段数据模型动态生成相应的表单控件。最后,在提交按钮的点击事件中,我们根据表单字段数据模型收集用户填写的表单数据,并进行校验处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值