I want to add a BackdropFilter()
to a SliverAppbar()
.
I want it to look something like the iOS App Library App Bar: https://cln.sh/eP8wfY.
Header sliver not floating over the list in a NestedScrollView does so but only to the header, I want the title
and the actions
to be visible while the background is blurred.
Thanks!
What the pages look like: https://cln.sh/vcCY4j.
Github Gist with my code: https://gist.github.com/HadyMash/21e7bd2f7e202de02837505e1c7363e9.
TL;DR I fixed the problem by using a normal AppBar()
since I didn't need a SliverAppBar()
. I made a custom app bar to fix the problem (see code at the end of the question).
I realised I didn't need a SilverAppBar()
because it will just stay floating
and pinned
. This made my life a whole lot easier since I could use an AppBar()
and set the extendBodyBehindAppBar
to true
in the Scaffold()
. This made it so that I wouldn't have to make a custom sliver widget as I am not familiar with making them.
My solution was to make a custom AppBar()
. I would have a Stack()
then put the blur effect and the AppBar()
above it.
https://github.com/flutter/flutter/issues/48212 shows that you can't use ShaderMasks()
with BackdropFilter()
s. To work around this I made a column with a bunch of BackdropFilter()
s. They would have decreasing sigma values to create the gradient effect I was looking for. This isn't very performant, however, and in heavier apps wouldn't work well. Making each block have the length of a single logical pixel was too heavy so I made it 2 logical pixels.
It can also be easily expanded, for example, by adding a fade effect as I did.
Here is what the result looks like.
Here is the code for the solution:
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);
}