sunarya-thito / shadcn_flutter

Shadcn/UI ported to Flutter (Unofficial)
https://sunarya-thito.github.io/shadcn_flutter/
BSD 3-Clause "New" or "Revised" License
50 stars 6 forks source link

Incorrect blurring of elements #71

Closed goottime closed 22 hours ago

goottime commented 6 days ago

Now, when blurring the background of elements, an image outside the widget is also taken. This blurs a widget that is not yet behind a widget with a blurred background. It may be worth blurring only the image that is behind the widget with a blurred background.

sunarya-thito commented 6 days ago

can you provide the screenshot or the reproducible code

goottime commented 6 days ago

As you can see, the image has not yet gone beyond the app bar, but there is already blur. I think we need to blur only the part that is directly behind the widget with a blurred background. For the test, I set surfaceOpacity to 0.1 and surfaceBlur to 25. The floatingHeader is enabled

https://github.com/user-attachments/assets/afdd8627-5ff1-4c3f-84dc-552216915600

cyberpwnn commented 5 days ago

My solution was to just disable the blur when the scroll extent was 0 pixels (or max extent when your blurring a bottom bar). This also works for sliver sticky headers, hacky but its really a limitation of how flutter image filters work, along with them being tremendously slow on most platforms. Better in impeller but still not "native like" and it still wont clip like your looking for.

In fact if you look close at native ios apps (any apple made app really), you can actually see they also turn off the blur on the bar when the view is not scrolled at all, they are doing the same thing.

If you were to clip the input image, the blur wouldnt look right on the edges, the edges would either fade to darkness on a fully bright background, OR it would mirror the edges if you did it that way. Basically there isnt really a good solution other than just disabling the blur when nothing is underneath it. Look at how the flutter cupertino widgets do it.

goottime commented 5 days ago

Okay, I realized that blurring only the part of the content that is behind the widget being blurred is not very effective.

But how can I then implement your solution, where the blur will be disabled if there are no scrollable elements behind it. I would like to achieve an effect similar to Cupertino.

By the way, have you thought about using a different blur method? I don't know how much more efficient it will be to use, but I recently came across the blurme library, which claims to speed up blurring by 30%.

cyberpwnn commented 5 days ago

Some tweaking is required, but you can control the blur from the scroll controller with a listener. I just kinda wrote this so idk if it will work right off the bat but this is generally how you could do it. This example does call set state on the entire screen instead of using a stream to only update the appbar itself but this should get the idea across just fine

class DynamicBarBlurThing extends StatefulWidget {
  const DynamicBarBlurThing({super.key});

  @override
  State<DynamicBarBlurThing> createState() => _DynamicBarBlurThingState();
}

class _DynamicBarBlurThingState extends State<DynamicBarBlurThing> {
  late ScrollController _controller;
  bool blurring = false;

  @override
  void initState() {
    // Setup Controller & Listener
    _controller = ScrollController();
    super.initState();
    _controller.addListener(() {
      if (!_controller.hasClients) return;

      // We SHOULD be blurring if the user has scrolled down at all
      bool shouldBlur = _controller.position.pixels > 0;

      // Only change the state & rebuild if the blur state has changed
      if (shouldBlur != blurring) {
        setState(() {
          blurring = shouldBlur;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        child: CustomScrollView(
          controller: _controller,
          slivers: [
            SliverPinnedHeader(
                child: AppBar(
              title: Text("Dynamic Bar Blur"),
              // We change the surface opacity to 1 when not blurring. 
              surfaceOpacity: blurring ? 0.5 : 1.0,
              // We change the surface blur to 16 when blurring.
              surfaceBlur: blurring ? 16 : 0,
            )),
            // Add your "content" below basically
            SliverList(
                delegate: SliverChildBuilderDelegate(
                    (context, _) => Text("Scroll down to see the bar blur")))
          ],
        ),
      );
}

As for blurme, I've also thought of doing a shader though i'm not really sure that would be better than blurme. The idea is awesome and i'm sure its faster however the amount of hacking & editing of packages to do this would be rather annoying. For example flutter_animate has a blur_in effect, so a lot of widgets have the blur filter at radius 0 (after they blur in) which destroys web performance even though nothing on the screen is blurred. If flutter_animate used blurme then it would be easier to implement. Basically i've kinda just accepted the low performance for now, knowing it will improve in the future either by me hacking it to work or by others updating their packages or flutter improving image filtered.

But the example above could be adapted to work with blurme just in the same way.

I also have a gif that is a different system but it basically does the same thing your looking to solve except its setting the app bars to red when blur is disabled to highlight it. This is done in slivers with pinned headers to demonstrate you can do this anywhere pretty easily

https://cdn.discordapp.com/attachments/315541537155186688/1277279087458517043/wow.gif?ex=66e79d19&is=66e64b99&hm=6e9b4ecd31d1b67b96d3d4637244c3a0fb8686535b7af92991d243ed56c8aa82&