is it possible to give gradient blur effect in Custom paint in Flutter like below picture?

2k Views Asked by At

I am coding the below output and i get the design i want but not able to get the blur effect inside the canvas.

This is the output i am trying to build,
Output i want

This is what i have tried, My Output

Here is the Code,

class MyCustomPainter extends CustomPainter {

@override



 void paint(Canvas canvas, Size size) {
    Paint paint0 = Paint()
      ..color = const Color.fromARGB(128,255,255,255)
    ..style = PaintingStyle.fill
    ..strokeWidth = 2.0;

Path path0 = Path();
path0.moveTo(size.width * 0.1500300, size.height * 0.1238500);
path0.cubicTo(
    size.width * 0.0037200,
    size.height * 0.1023500,
    size.width * 0.0522600,
    size.height * 0.7552500,
    size.width * 0.1500500,
    size.height * 0.8761750);
path0.cubicTo(
    size.width * 0.2767600,
    size.height * 0.8761750,
    size.width * 0.7234100,
    size.height * 0.8735500,
    size.width * 0.8501100,
    size.height * 0.8735500);
path0.cubicTo(
    size.width * 0.9464300,
    size.height * 0.7575750,
    size.width * 0.9946900,
    size.height * 0.0944750,
    size.width * 0.8496900,
    size.height * 0.1268750);
path0.cubicTo(
    size.width * 0.7230200,
    size.height * 0.1268750,
    size.width * 0.5303400,
    size.height * 0.1263500,
    size.width * 0.1500300,
    size.height * 0.1238500);
path0.close();
canvas.drawPath(path0, paint0);


}

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

Thanks in advance!

5

There are 5 best solutions below

1
On BEST ANSWER

You don't need to bother yourself with a CustomPainter everything can be done with a Container and a BackdropFilter widgets.

Code sample

class BlurredBottomBar extends StatelessWidget {
  const BlurredBottomBar({
    super.key,
    required this.items,
    required this.currentIndex,
    required this.onItemTapped,
  });

  final int currentIndex;
  final void Function(int index) onItemTapped;
  final List<IconData> items;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final bottomNavigationBarTheme = theme.bottomNavigationBarTheme;

    return Container(
      margin: const EdgeInsets.all(12),
      padding: const EdgeInsets.all(12),
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.white,
          strokeAlign: BorderSide.strokeAlignOutside,
        ),
        borderRadius: const BorderRadius.vertical(
          top: Radius.circular(9),
          bottom: Radius.circular(36),
        ),
      ),
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            for (int i = 0; i < items.length; i++)
              IconButton(
                onPressed: () => onItemTapped(i),
                icon: Icon(
                  items[i],
                  color: i == currentIndex
                      ? bottomNavigationBarTheme.selectedItemColor
                      : bottomNavigationBarTheme.unselectedItemColor,
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Usage

Scaffold(
  extendBody: true,
  bottomNavigationBar: BlurredBottomBar(
    currentIndex: 0,
    items: const [
      Icons.home,
      Icons.grid_view,
      Icons.notifications_outlined,
      Icons.settings_outlined,
    ],
    onItemTapped: (index) {
      // ...
    },
  ),
  body: ...,
);

Screenshot

enter image description here

You can try the full example on zapp.run

3
On

I have been able to get up to the very closed result you needed. Especially the blur effect. The key elements used are ClipRRect, ClipPath, BackdropFilter. I have used other elements as well, but it's upto you. The idea is to clip the rectangle diagonally, so that one side is blurred and other side is transparent. Also clipping restricts the effect of backdrop filter's outside the rectangle, otherwise it causes blur effect on the entire screen. If you play around a little with various color values with opacity, you can get the exact result. You can also look for the results at this Dartpad link

main.dart file.

import 'dart:ui';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: false,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  Widget drawRowItem(BuildContext context) {
    return Expanded(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 12),
        decoration: BoxDecoration(
            borderRadius: const BorderRadius.all(Radius.circular(16)),
            border: Border.all(
                width: 2, color: const Color.fromARGB(255, 40, 40, 40))),
        height: 200,
        child: const Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Icon(
                Icons.home_filled,
                color: Colors.white,
              ),
              SizedBox(height: 8),
              Text(
                "Samle Item",
                style: TextStyle(
                  color: Colors.white,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget drawRowItems(BuildContext context) {
    return Container(
      height: MediaQuery.of(context).size.width / 2 - 56,
      width: MediaQuery.of(context).size.width / 2 - 56,
      margin: const EdgeInsets.symmetric(horizontal: 22),
      child: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          drawRowItem(context),
          const SizedBox(
            width: 16,
          ),
          drawRowItem(context)
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    List<int> items = List.filled(40, 0);
    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        fit: StackFit.expand,
        children: [
          ListView(
            children: items.map((e) {
              return drawRowItems(context);
            }).toList(),
          ),
          Positioned(
            bottom: 18,
            left: 18,
            right: 18,
            height: 86,
            child: Container(
              decoration: BoxDecoration(
                  border: Border.all(
                      width: 2, color: const Color.fromARGB(255, 40, 40, 40)),
                  borderRadius: const BorderRadius.only(
                      topRight: Radius.circular(24),
                      topLeft: Radius.circular(24),
                      bottomLeft: Radius.circular(52),
                      bottomRight: Radius.circular(52)),
                 color: const Color.fromARGB(100, 40, 40, 40)
              ),
              child: ClipRRect(
                borderRadius: const BorderRadius.only(
                    topRight: Radius.circular(24),
                    topLeft: Radius.circular(24),
                    bottomLeft: Radius.circular(52),
                    bottomRight: Radius.circular(52)),
                child: ClipPath(
                  clipper: MyCustomClipper(),
                  child: BackdropFilter(
                    filter: ImageFilter.blur(sigmaY: 5, sigmaX: 5),
                    child: Container(),
                  ),
                ),
              ),
            ),
          ),
          const Positioned(
            bottom: 18,
            left: 22,
            right: 22,
            height: 86,
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Icon(
                  Icons.home_outlined,
                  color: Colors.amber,
                ),
                Icon(
                  Icons.apps,
                  color: Colors.white,
                ),
                Icon(
                  Icons.notifications_outlined,
                  color: Colors.white,
                ),
                Icon(
                  Icons.settings_outlined,
                  color: Colors.white,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class MyCustomClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(size.width, 0);
    path.lineTo(64,size.height);
    path.lineTo(0,size.height);
    path.lineTo(0,0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return true;
  }
}

enter image description here

1
On

First you will have to pass extendBody as true to the Scaffold, so that the body will extend over the bottom navigation bar, then add the bottomNavigationBar with BackDropFilter like this,

bottomNavigationBar: Container(
        margin: const EdgeInsets.all(12.0),
        child: ClipRRect(
          borderRadius: const BorderRadius.only(
            topLeft: Radius.circular(12.0),
            topRight: Radius.circular(12.0),
            bottomLeft: Radius.circular(36.0),
            bottomRight: Radius.circular(36.0),
          ),
          child: BackdropFilter(
            filter: ImageFilter.blur(
              sigmaX: 2.0,
              sigmaY: 2.0,
            ),
            child: Container(
              decoration: BoxDecoration(color: Colors.orange.withOpacity(0.4)),
              child: BottomNavigationBar(
                elevation: 0,
                type: BottomNavigationBarType.fixed,
                unselectedItemColor: Colors.blue,
                backgroundColor: Colors.transparent,
                onTap: (int index) {
                  setState(() {
                    selectedIndex = index;
                  });
                },
                currentIndex: selectedIndex,
                items: const <BottomNavigationBarItem>[
                  BottomNavigationBarItem(
                      icon: Icon(Icons.home), label: "Home"),
                  BottomNavigationBarItem(
                      icon: Icon(Icons.person), label: "Profile"),
                ],
              ),
            ),
          ),
        ),
      ),

Here I have tried to create the UI shared in the question, I have added 2 BottomNavigationBarItem inside the BackDropFilter in order to blur the background of the bottom navigation bar. here is full code for reference.

2
On

I don’t know if you have a strict requirement to use a custom paint but you can achieve that effect using the BackdropFilter and using an ImageFilter.blur as your filter value.

2
On

Use BackdropFilter. Demo usage:

            BackdropFilter(
                filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
                child: Container(
                  width: 334.66,
                  height: 212.66,
                  color: Colors.black.withOpacity(0.2),
                  child: Center(
                    child: Text(
                      'Demo Text',
                  ),
                ),
              ),