基于Flutter实现动态高斯模糊的流程步骤
作者:易秋
效果如下
实现思路
AppBar
本质上是一个Widget
,我们不直接通过Scaffold
的appBar
属性来设置AppBar
,而是将我们需要的AppBar
放在body
属性里面,至于为什么不能放在appBar
属性里面呢,最主要的原因是,Scaffold
的appBar
属性是顶级组件,在appBar
以外的任何组件都无法覆盖或置于其下,而我们的主要实现思路是通过Stack
组件的堆叠以及Positioned
的位置设置来实现此效果
实现步骤
创建一个工程
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), ); } }
创建自定义嵌套导航路由
App结构构建
为了使代码更好的管理和维护,我们对页面使用三层架构模式,即分为state
、logic
、view
三层,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}); @override 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}); @override State<AppMainPage> createState() => _AppMainPageState(); } class _AppMainPageState extends State<AppMainPage> with TickerProviderStateMixin { final logic = Get.put(AppMainLogic()); final state = Get.find<AppMainLogic>().state; @override void initState() { super.initState(); } @override 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}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final logic = Get.put(HomeLogic()); final state = Get.find<HomeLogic>().state; @override 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}); @override State<AppMainPage> createState() => _AppMainPageState(); } class _AppMainPageState extends State<AppMainPage> with TickerProviderStateMixin { final logic = Get.put(AppMainLogic()); final state = Get.find<AppMainLogic>().state; @override void initState() { super.initState(); logic.tabControllerInit(this); } @override void dispose() { super.dispose(); state.tabController.dispose(); state.nestedController.dispose(); } @override 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}); @override 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}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final logic = Get.put(HomeLogic()); final state = Get.find<HomeLogic>().state; @override 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实现动态高斯模糊的流程步骤的详细内容,更多关于Flutter动态高斯模糊的资料请关注脚本之家其它相关文章!