2

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.

3 Answers3

2

NOTE: getting hard time on color, even after spending hours of time.

  • you need to change colors
  • of you find some area problem that maybe because of safeArea or CupertinoNavBar.
  • you can remove/change color on shadow, i'm giving too much on test purpose.
  • All you have to play with Colors and LinearGradient

OutPut

enter image description here


Here is my concept:

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

Demo


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")),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • Hello, thank you very much for the help! Is there a way to do this to a `SliverAppBar()`? This is my fault as I should have included this information earlier. The first page has a `NestedScrollView()` with Sliver Headers, then I have a body which contains some conditional logic and a `ListView.builder()`. The second page has a `NestedScrollView()` with a `SliverAppBar()` as well. Here is what they look like: https://cln.sh/vcCY4j. Here is a gist with the code: https://gist.github.com/HadyMash/21e7bd2f7e202de02837505e1c7363e9. –  Aug 19 '21 at 09:25
  • https://github.com/flutter/flutter/issues/48212#issuecomment-750785885. This was a workaround I was considering but I wasn't sure how to add a `BackdropFilter()` to a `SliverAppBar()`. The problem is I don't know how to add BackdropFilters to a `SliverAppBar()` or how to include it in a `Stack()`. –  Aug 19 '21 at 09:42
0

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

0
SliverAppBar(
  primary: false,
  toolbarHeight: kToolbarHeight * 1.5,
  floating: true,
  snap: true,
  backgroundColor: Colors.black45,
  titleSpacing: 0.0,
  title: BackdropFilter(
    filter: ImageFilter.blur(sigmaX: 36.0, sigmaY: 36.0),
    child: SizedBox(
      width: MediaQuery.of(context).size.width,
      height: kToolbarHeight * 1.5,
    ),
  ),
)

You can change toolbarHeight to your liking, but the child of the BackdropFilter must have the same height.