Flutter: How to add BackdropFilter to SliverAppBar

Issue

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!

Edit

What the pages look like: https://cln.sh/vcCY4j.

Github Gist with my code: https://gist.github.com/HadyMash/21e7bd2f7e202de02837505e1c7363e9.

Solution

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);
}

Answered By – Hady

Answer Checked By – Pedro (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.