Flutter:如何将 BackdropFilter 添加到 SliverAppBar
Flutter: How to add BackdropFilter to SliverAppBar
我想添加一个 BackdropFilter()
到一个 SliverAppbar()
。
我希望它看起来像 iOS 应用程序库应用栏:https://cln.sh/eP8wfY。
Header sliver not floating over the list in a NestedScrollView 这样做,但只针对 header,我希望 title
和 actions
在背景模糊时可见。
谢谢!
编辑
页面的外观:https://cln.sh/vcCY4j。
Github 我的代码要点:https://gist.github.com/HadyMash/21e7bd2f7e202de02837505e1c7363e9.
注意:即使在花费数小时之后,也会在颜色上遇到困难。
- 你需要改变颜色
- 你们中的一些人发现一些区域问题可能是因为 safeArea 或 CupertinoNavBar。
- 你可以 remove/change 给阴影上色,我给的太多是为了测试目的。
- 所有你必须玩的颜色和
LinearGradient
OutPut
这是我的概念:
Stack
- backgroundImage
- Container with white.3
- CustomScrollView
- SliverToBoxAdapter 2x kToolbarHeight for extra height for GridList,
- SliverGrid
- LinearGradient 2xkToolbarHeight for fadeEffect on upper scroll
- our widget TextField or anything
演示
class Body extends StatelessWidget {
const Body({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: [
Container(
decoration: BoxDecoration(
// color: Colors.white.withOpacity(.3),
image: DecorationImage(
image: AssetImage("assets/me.jpg"),
fit: BoxFit.cover,
),
),
child: Container(),
),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.3),
),
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: kToolbarHeight * 2,
),
),
SliverPadding(
padding: EdgeInsets.all(20),
sliver: SliverGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: [
...List.generate(
12,
(index) => Container(
decoration: BoxDecoration(
color: index % 3 == 0
? Colors.deepPurple
: index % 3 == 1
? Colors.deepOrange
: Colors.amberAccent,
borderRadius: BorderRadius.circular(12),
),
))
],
),
)
],
),
),
Align(
alignment: Alignment.topCenter,
child: Container(
height: kToolbarHeight * 2,
width: constraints.maxWidth,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.grey,
Colors.white.withOpacity(.7),
],
),
),
child: Text(""),
),
),
Positioned(
top: kTextTabBarHeight * 1.122,
/// need to tweek
left: 20,
right: 20,
child: Container(
height: kToolbarHeight,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white70,
boxShadow: [
BoxShadow(
blurRadius: 12,
spreadRadius: 6,
color: Colors.black54,
offset: Offset(0, 12))
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
print("boosm");
},
child: Text("Tap")),
],
),
),
),
],
),
),
);
}
}
TL;DR 我使用普通 AppBar()
解决了这个问题,因为我不需要 SliverAppBar()
。我制作了一个自定义应用程序栏来解决问题(请参阅问题末尾的代码)。
我意识到我不需要 SilverAppBar()
,因为它只会保留 floating
和 pinned
。这让我的生活变得轻松多了,因为我可以使用 AppBar()
并在 Scaffold()
中将 extendBodyBehindAppBar
设置为 true
。这使得我不必制作自定义的条子小部件,因为我不熟悉制作它们。
我的解决方案是定制 AppBar()
。我会有一个 Stack()
,然后将模糊效果和 AppBar()
放在它上面。
https://github.com/flutter/flutter/issues/48212 表明您不能将 ShaderMasks()
与 BackdropFilter()
一起使用。为了解决这个问题,我制作了一个包含一堆 BackdropFilter()
的专栏。他们会降低 sigma 值来创建我正在寻找的渐变效果。然而,这不是很好的性能,并且在较重的应用程序中效果不佳。使每个块具有单个逻辑像素的长度太重,所以我将其设为 2 个逻辑像素。
它也可以很容易地扩展,例如,像我一样添加淡入淡出效果。
结果如下。
- 之前:https://cln.sh/vcCY4j
- 之后:
- https://cln.sh/vNZVJW(包括不透明效果 (0.8))
- https://cln.sh/jfH7OP(降低不透明度效果 (0.5))
- https://cln.sh/nXzPLe(无不透明效果)
这是解决方案的代码:
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class BlurredAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
/// An `AppBar()` which has a blur effect behind it which fades in to hide it
/// until content appears behind it. This has a similar effect to the iOS 14
/// App Library app bar. It also has the possibility of having a fade effect to
/// redude the opacity of widgets behind the `BlurredAppBar()` using a `LinearGradient()`.
const BlurredAppBar({required this.title, this.actions, Key? key})
: super(key: key);
/// The height of the `AppBar()`
final double height = 56;
/// Returns a `List<Widget>` of `BackdropFilter()`s which have decreasing blur values.
/// This will create the illusion of a gradient blur effect as if a `ShaderMask()` was used.
List<Widget> _makeBlurGradient(double height, MediaQueryData mediaQuery) {
List<Widget> widgets = [];
double length = height + mediaQuery.padding.top;
for (int i = 1; i <= (length / 2); i++) {
widgets.add(
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: max(((length / 2) - i.toDouble()) / 2, 0),
sigmaY: min(5, max(((length / 2) - i.toDouble()) / 2, 0)),
),
child: SizedBox(
height: 2,
width: mediaQuery.size.width,
),
),
),
);
}
return widgets;
}
@override
Widget build(BuildContext context) {
final MediaQueryData mediaQuery = MediaQuery.of(context);
return Stack(
children: [
// BackdropFilters
SizedBox(
height: height + mediaQuery.padding.top,
child: Column(
children: _makeBlurGradient(height, mediaQuery),
),
),
// Fade effect.
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.5, 1],
colors: [
Colors.white.withOpacity(0.8),
Colors.white.withOpacity(0),
],
),
),
),
// AppBar
AppBar(
title: Text(
title,
style: Theme.of(context).textTheme.headline3,
),
automaticallyImplyLeading: false,
actions: actions,
),
],
);
}
@override
Size get preferredSize => Size.fromHeight(height);
}
我想添加一个 BackdropFilter()
到一个 SliverAppbar()
。
我希望它看起来像 iOS 应用程序库应用栏:https://cln.sh/eP8wfY。
Header sliver not floating over the list in a NestedScrollView 这样做,但只针对 header,我希望 title
和 actions
在背景模糊时可见。
谢谢!
编辑
页面的外观:https://cln.sh/vcCY4j。
Github 我的代码要点:https://gist.github.com/HadyMash/21e7bd2f7e202de02837505e1c7363e9.
注意:即使在花费数小时之后,也会在颜色上遇到困难。
- 你需要改变颜色
- 你们中的一些人发现一些区域问题可能是因为 safeArea 或 CupertinoNavBar。
- 你可以 remove/change 给阴影上色,我给的太多是为了测试目的。
- 所有你必须玩的颜色和
LinearGradient
OutPut
这是我的概念:
Stack
- backgroundImage
- Container with white.3
- CustomScrollView
- SliverToBoxAdapter 2x kToolbarHeight for extra height for GridList,
- SliverGrid
- LinearGradient 2xkToolbarHeight for fadeEffect on upper scroll
- our widget TextField or anything
演示
class Body extends StatelessWidget {
const Body({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: [
Container(
decoration: BoxDecoration(
// color: Colors.white.withOpacity(.3),
image: DecorationImage(
image: AssetImage("assets/me.jpg"),
fit: BoxFit.cover,
),
),
child: Container(),
),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.3),
),
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: kToolbarHeight * 2,
),
),
SliverPadding(
padding: EdgeInsets.all(20),
sliver: SliverGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: [
...List.generate(
12,
(index) => Container(
decoration: BoxDecoration(
color: index % 3 == 0
? Colors.deepPurple
: index % 3 == 1
? Colors.deepOrange
: Colors.amberAccent,
borderRadius: BorderRadius.circular(12),
),
))
],
),
)
],
),
),
Align(
alignment: Alignment.topCenter,
child: Container(
height: kToolbarHeight * 2,
width: constraints.maxWidth,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.grey,
Colors.white.withOpacity(.7),
],
),
),
child: Text(""),
),
),
Positioned(
top: kTextTabBarHeight * 1.122,
/// need to tweek
left: 20,
right: 20,
child: Container(
height: kToolbarHeight,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white70,
boxShadow: [
BoxShadow(
blurRadius: 12,
spreadRadius: 6,
color: Colors.black54,
offset: Offset(0, 12))
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
print("boosm");
},
child: Text("Tap")),
],
),
),
),
],
),
),
);
}
}
TL;DR 我使用普通 AppBar()
解决了这个问题,因为我不需要 SliverAppBar()
。我制作了一个自定义应用程序栏来解决问题(请参阅问题末尾的代码)。
我意识到我不需要 SilverAppBar()
,因为它只会保留 floating
和 pinned
。这让我的生活变得轻松多了,因为我可以使用 AppBar()
并在 Scaffold()
中将 extendBodyBehindAppBar
设置为 true
。这使得我不必制作自定义的条子小部件,因为我不熟悉制作它们。
我的解决方案是定制 AppBar()
。我会有一个 Stack()
,然后将模糊效果和 AppBar()
放在它上面。
https://github.com/flutter/flutter/issues/48212 表明您不能将 ShaderMasks()
与 BackdropFilter()
一起使用。为了解决这个问题,我制作了一个包含一堆 BackdropFilter()
的专栏。他们会降低 sigma 值来创建我正在寻找的渐变效果。然而,这不是很好的性能,并且在较重的应用程序中效果不佳。使每个块具有单个逻辑像素的长度太重,所以我将其设为 2 个逻辑像素。
它也可以很容易地扩展,例如,像我一样添加淡入淡出效果。
结果如下。
- 之前:https://cln.sh/vcCY4j
- 之后:
- https://cln.sh/vNZVJW(包括不透明效果 (0.8))
- https://cln.sh/jfH7OP(降低不透明度效果 (0.5))
- https://cln.sh/nXzPLe(无不透明效果)
这是解决方案的代码:
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class BlurredAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
/// An `AppBar()` which has a blur effect behind it which fades in to hide it
/// until content appears behind it. This has a similar effect to the iOS 14
/// App Library app bar. It also has the possibility of having a fade effect to
/// redude the opacity of widgets behind the `BlurredAppBar()` using a `LinearGradient()`.
const BlurredAppBar({required this.title, this.actions, Key? key})
: super(key: key);
/// The height of the `AppBar()`
final double height = 56;
/// Returns a `List<Widget>` of `BackdropFilter()`s which have decreasing blur values.
/// This will create the illusion of a gradient blur effect as if a `ShaderMask()` was used.
List<Widget> _makeBlurGradient(double height, MediaQueryData mediaQuery) {
List<Widget> widgets = [];
double length = height + mediaQuery.padding.top;
for (int i = 1; i <= (length / 2); i++) {
widgets.add(
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: max(((length / 2) - i.toDouble()) / 2, 0),
sigmaY: min(5, max(((length / 2) - i.toDouble()) / 2, 0)),
),
child: SizedBox(
height: 2,
width: mediaQuery.size.width,
),
),
),
);
}
return widgets;
}
@override
Widget build(BuildContext context) {
final MediaQueryData mediaQuery = MediaQuery.of(context);
return Stack(
children: [
// BackdropFilters
SizedBox(
height: height + mediaQuery.padding.top,
child: Column(
children: _makeBlurGradient(height, mediaQuery),
),
),
// Fade effect.
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.5, 1],
colors: [
Colors.white.withOpacity(0.8),
Colors.white.withOpacity(0),
],
),
),
),
// AppBar
AppBar(
title: Text(
title,
style: Theme.of(context).textTheme.headline3,
),
automaticallyImplyLeading: false,
actions: actions,
),
],
);
}
@override
Size get preferredSize => Size.fromHeight(height);
}