一个App加上高斯模糊会形成一种高级的感觉,本文将介绍如何制作一个根据背景内容来动态高斯模糊,效果如下
实现思路
AppBar
本质上是一个Widget
,我们不直接通过Scaffold
的appBar
属性来设置AppBar
,而是将我们需要的AppBar
放在body
属性里面,至于为什么不能放在appBar
属性里面呢,最主要的原因是,Scaffold
的appBar
属性是顶级组件,在appBar
以外的任何组件都无法覆盖或置于其下,而我们的主要实现思路是通过Stack
组件的堆叠以及Positioned
的位置设置来实现此效果
实现步骤
创建一个工程
dart
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>
分层模式
yaml
dependencies:
flutter:
sdk: flutter
# 路由管理
get: ^4.6.5
# 屏幕适配工具
flutter_screenutil: ^5.9.0
在main.dart
中更改为如下内容
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
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
dart
class AppMainState {
AppMainState();
}
logic.dart
dart
import 'package:get/get.dart';
import 'state.dart';
class AppMainLogic extends GetxController {
final AppMainState state = AppMainState();
}
view.dart
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
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
dart
class HomeState {
HomeState() {
///Initialize variables
}
}
logic.dart
dart
import 'package:get/get.dart';
import 'state.dart';
class HomeLogic extends GetxController {
final HomeState state = HomeState();
}
view.dart
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
,然后我们接着完成嵌套导航结构
自定义嵌套导航
在需要用到嵌套导航的页面引入如下代码
dart
Navigator(
key: Get.nestedKey(1),
initialRoute: NestedController.home,
onGenerateRoute: state.nestedController.onGenerateRoute,
)
如我们想在appmain下的view.dart中引入,首先state层建立我们需要的数据,这里我使用了一个第三方的TabBar组件,大家想用官方的组件也可以,只需稍做修改即可
导入第三方组件依赖
yaml
dependencies:
flutter:
sdk: flutter
# 路由管理
get: ^4.6.5
# 屏幕适配工具
flutter_screenutil: ^5.9.0
# bruno ui组件
bruno: ^3.4.3
appmain/state.dart
中修改如下
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创建另外两个页面,也可以根据自己的需求进行更改
dart
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
中稍作修改
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
工具类内容如下,此工具类是为了更好的管理横竖屏的像素而建立的类
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
中添加嵌套导航的组件以及一些样式的优化,代码如下
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
的类
内容为
dart
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,
),
);
}
}
使用方法
dart
BlurRectWidget(
singmaX: 20,
singmaY: 20,
widget // 这里换成你想包裹的widget
)
比如我们在appmain/view.dart
中将BrnAppBar
包裹起来
dart
BlurRectWidget(
singmaX: 20,
singmaY: 20,
BrnAppBar( ... )
),
运行查看效果
可以看到已有高斯模糊效果,那是不是动态的呢,我们在home/view
里面添加一些内容来验证
home/view.dart
内容修改如下
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类, 此类基于github.com/iofod/flutt...做修改,因为原作者的CSSFilter对组件进行模糊而不是对背景进行模糊,所以我做修改了
在common/utils
目录下创建如下文件
base.dart
内容如下
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
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
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
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
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;
}
}
至此封装完毕使用方法
dart
BackdropCSSFilter.blur(
value: 10,
child: BrnAppBar( ... )
),
效果同理
去除底部小白条
至于如何去除底部小白条,可以参考我的一篇博客Flutter TabBar下方白条隐藏 去除白条后的效果