Site Juggler
  • React
  • Flutter
No Result
View All Result
Site Juggler
  • React
  • Flutter
No Result
View All Result
Site Juggler
No Result
View All Result
Home Flutter

Neumorphism Animation: How to Make a 3D Animated Button in Flutter Complete Guide

Hemant Singh by Hemant Singh
May 13, 2023
in Flutter
0 0
0
beautiful Neumorphic style menu button in Flutter

beautiful Neumorphic style menu button in Flutter

0
SHARES
43
VIEWS
Share on FacebookShare on Twitter

Neumorphism Animation or Style is a popular design trend that has been gaining a lot of attention recently. It involves creating user interface elements that appear to be raised from the background and cast a slight shadow, giving them a three-dimensional look. In this article, we will be looking at how to create a Neumorphic animated button in Flutter using the Dart programming language. Even if you’re new to Flutter or haven’t worked with Neumorphism before, don’t worry! We’ll take a step-by-step approach to building the button and explain the code along the way.

If you want to clear the basics of how Neumorphism works, you can check out our previous article on the topic and then come back to understand this code in more detail.

Table of Contents

  • Implementing a Neumorphic Animation in Flutter
    • Let’s Begin With The Animation
    • Let’s Fix the Shadow Problem
  • Output
  • Full Source Code
  • Conclusion

Implementing a Neumorphic Animation in Flutter

Creating a Neumorphism Button

First, define a custom widget class called NeumorphismAnimatedButton that extends StatefulWidget. This class has a state class _NeumorphismAnimatedButtonState that extends State and is responsible for building the user interface.

class NeumorphismAnimatedButton extends StatefulWidget {
  const NeumorphismAnimatedButton({Key? key}) : super(key: key);

  @override
  State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}

class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> { 
   Widget build(BuildContext context) {
          return ///your User Interface
  }
}

Inside the _NeumorphismAnimatedButtonState class, there are several variables that keep track of the state of the button. These include isClicked and isDarkMode, which stores whether the button has been clicked and whether the app is currently in dark mode, respectively. There are also variables to define colors and offsets used for the button’s styling.

class NeumorphismAnimatedButton extends StatefulWidget {
  const NeumorphismAnimatedButton({Key? key}) : super(key: key);

  @override
  State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}

class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> { 

     // state variables
  double turns = 0.0;
  bool isClicked = false;
  bool isDarkMode = true;
  Color customWhiteColor = Colors.white60;
  Color customBlackColor = Colors.black54;


   Widget build(BuildContext context) {
          return ///your User Interface
  }
}

The _toggleTheme a function is called when the user toggles the theme switch in the app bar. This function toggles the isDarkMode variable and updates the UI using the setState function.

  void _toggleTheme() {
    setState(() {
      isDarkMode = !isDarkMode;
    });
  }

The build function returns a Scaffold widget with an app bar and a body that consists of a centered Stack widget. The Stack widget contains a Padding widget with a AnimatedContainer widget inside. The AnimatedContainer widget is wrapped with a GestureDetector widget that listens for taps on the button.

Scaffold(
      backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set background color based on current theme
      appBar: AppBar(
        title:  Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset("assets/images/sitejuggler.png", // add an image to the app bar
              height: 50,width: 50,),
            Text("SiteJuggler"), // add app title
          ],
        ),
        actions: [
          Switch( // add a switch button to toggle theme
            value: isDarkMode,
            onChanged: (value) {
              _toggleTheme();
            },
            activeColor: Colors.white,
            inactiveThumbColor: Colors.white,
          ),
        ],
      ),
      body: Center(
        child: Stack(
          children:[
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: AnimatedContainer(
                duration: Duration(milliseconds: 200),
                child: GestureDetector(
                  onTap: (){

                  },
                  child: FittedBox(
                    child: Container(
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(25),
                          color: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set button color based on current theme
                          boxShadow:[
                            BoxShadow(
                              blurRadius: blur,
                              offset: Offset(20,-20),
                              color: isDarkMode?const Color(0xff35393f):Colors.white, // use ternary operator to set shadow color based on current theme
                            ),
                            BoxShadow(
                              blurRadius: blur,
                              offset: Offset(-20,-20),
                              color: isDarkMode?const Color(0xff23262a):Colors.grey, // use ternary operator to set shadow color based on current theme
                            )
                          ]
                      ),
                      child: SizedBox(
                        height: 100,
                        width: 100,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );

The button is styled using a BoxDecoration that adds a border-radius, background color, and two BoxShadow widgets to create a Neumorphic effect. The color and offset of the shadows change depending on whether the app is in dark mode or not. The height and width of the button are set using a SizedBox widget.

class NeumorphismAnimatedButton extends StatefulWidget {
  const NeumorphismAnimatedButton({Key? key}) : super(key: key);

  @override
  State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}

class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton> { 

  // state variables
  double turns = 0.0;
  bool isClicked = false;
  bool isDarkMode = true;
  Color customWhiteColor = Colors.white60;
  Color customBlackColor = Colors.black54;

  // function to toggle theme between light and dark mode
  void _toggleTheme() {
    setState(() {
      isDarkMode = !isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    const backgroundColor =  Colors.white70; // set a constant background color for light mode
    Offset distance = const Offset(30,30);
    Offset distance2 = const Offset(-30,30);
    double blur = 40.0;

    return Scaffold(
      backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set background color based on current theme
      appBar: AppBar(
        title:  Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset("assets/images/sitejuggler.png", // add an image to the app bar
              height: 50,width: 50,),
            Text("SiteJuggler"), // add app title
          ],
        ),
        actions: [
          Switch( // add a switch button to toggle theme
            value: isDarkMode,
            onChanged: (value) {
              _toggleTheme();
            },
            activeColor: Colors.white,
            inactiveThumbColor: Colors.white,
          ),
        ],
      ),
      body: Center(
        child: Stack(
          children:[
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: AnimatedContainer(
                duration: Duration(milliseconds: 200),
                child: GestureDetector(
                  onTap: (){

                  },
                  child: FittedBox(
                    child: Container(
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(25),
                          color: isDarkMode?const Color(0xff2e3239):backgroundColor, // use ternary operator to set button color based on current theme
                          boxShadow:[
                            BoxShadow(
                              blurRadius: blur,
                              offset: Offset(20,-20),
                              color: isDarkMode?const Color(0xff35393f):Colors.white, // use ternary operator to set shadow color based on current theme
                            ),
                            BoxShadow(
                              blurRadius: blur,
                              offset: Offset(-20,-20),
                              color: isDarkMode?const Color(0xff23262a):Colors.grey, // use ternary operator to set shadow color based on current theme
                            )
                          ]
                      ),
                      child: SizedBox(
                        height: 100,
                        width: 100,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

well, we have set up the basic structure and styling of a Neumorphic animated button that can be customized further to fit specific design needs.

Neumorphism Style button
Neumorphism Style button

Let’s Begin With The Animation

Creating Animated Icon

first We add the Center widget to position the AnimatedIcon widget at the center of the screen.

Then we add the AnimatedIcon widget with the following properties:

  • The icon property is set to AnimatedIcons.menu_close, which is an AnimatedIconData object that displays the menu icon when the button is in its default state, and animates into a close icon when the button is clicked.
  • The progress property is set to _controller, which is a AnimationController object that controls the animation of the icon. We’ll update this controller when the button is clicked to start the animation.
  • The size property is set to 20, which sets the size of the icon.
  • The color property is set to isDarkMode? Colors.white:Colors.black, which sets the color of the icon based on the value of isDarkMode. If isDarkMode is true, the icon color will be white, otherwise it will be black.
Center(
  child: AnimatedIcon(
    icon: AnimatedIcons.menu_close,
    progress: _controller,
    size: 20,
    color:  isDarkMode? Colors.white:Colors.black,
  )
)

To create animations, we need to use an AnimationController. In this code, we are defining a variable _controller of type AnimationController, which is used to control the animation.

 late AnimationController _controller;

The late keyword is used to tell the Dart compiler that the variable may be initialized at a later time. We are initializing the _controller variable in the initState method using the vsync argument, which ensures that the animation is synchronized with the device’s vertical sync.

  @override
  void initState() {
    _controller = AnimationController(
        duration: const Duration(milliseconds: 800),
        vsync:this
    );
    super.initState();
  }

We are also disposing of the controller in the dispose method to free up resources when the widget is removed from the tree.

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
Neumorphism Style icon button
Neumorphism Style icon button

Implementing Animated Rotation

  • We use the AnimatedRotation widget to animate the rotation of the button when clicked. This widget takes three properties: curve, turns, and duration.
  • The curve property specifies the easing curve for the animation, which determines how fast the animation starts and ends.
  • The turns property specifies how many turns the button should rotate, which is set to a quarter turn or 1/4.
  • The duration property specifies how long the animation should take, which is set to one second.
  • We wrap the button with a GestureDetector a widget that listens for taps on the button.
  • When the button is tapped, the onTap function is called, which checks whether the button has been clicked or not using the isClicked variable.
  • If the button has been clicked, we set the turns variable to rotate the button back a quarter turn using -1/4, and we call _controller.reverse() to reverse the rotation animation.
  • If the button has not been clicked, we set the turns variable to rotate the button forward a quarter turn using 1/4, and we call _controller.forward() to start the rotation animation.
  • Finally, we set the isClicked variable to the opposite of its current value to toggle the button state.
  • The button is styled using a Container a widget that has a BoxDecoration with a rounded border and two BoxShadow widgets to create the Neumorphic effect. The color property of the container depends on whether the app is in dark mode or not.
  • The height and width of the button are set using a SizedBox widget.
  • The Center widget contains an AnimatedIcon a widget that displays the menu icon, which is animated using the _controller variable. The color property of the icon depends on whether the app is in dark mode or not.
AnimatedRotation(
  curve: Curves.easeOutExpo, // An easing curve that starts fast and then gets slower at the end
  turns: turns, // The amount of rotation (in terms of number of turns)
  duration: const Duration(seconds: 1), // The duration of the rotation animation
  child: GestureDetector(
    onTap: (){
      if(isClicked){ // If the button is clicked
        setState(() {
          turns -= 1/4; // Reduce the rotation by one quarter
          _controller.reverse(); // Reverse the rotation animation
        });
      }
      else{ // If the button is not clicked
        setState(() {
          turns += 1/4; // Increase the rotation by one quarter
          _controller.forward(); // Start the rotation animation
        });
      }
      isClicked = !isClicked; // Toggle the button clicked state
    },
    child: FittedBox(
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(25),
          color: isDarkMode ? const Color(0xff2e3239) : backgroundColor,
          boxShadow:[
            BoxShadow(
              blurRadius: blur,
              offset: -distance,
              color: isDarkMode ? const Color(0xff35393f) : Colors.white,
            ),
            BoxShadow(
              blurRadius: blur,
              offset: distance,
              color: isDarkMode ? const Color(0xff23262a) : Colors.grey,
            )
          ]
        ),
        child: SizedBox(
          height: 100,
          width: 100,
          child: Center(
            child: AnimatedIcon(
              icon: AnimatedIcons.menu_close, // The icon to display
              progress: _controller, // The progress of the rotation animation
              size: 20,
              color: isDarkMode ? Colors.white : Colors.black, // The color of the icon
            )
          ),
        ),
      ),
    ),
  ),
),

Creating Neumorphism Animation Page

We are now adding another AnimatedContainer that will be shown when the button is clicked. This container will cover the whole screen with a background color and will be hidden when the button is not clicked.

// AnimatedContainer to show/hide the full screen background when button is clicked
AnimatedContainer(
  height: isClicked ? MediaQuery.of(context).size.height : 0,
  width: isClicked ? MediaQuery.of(context).size.width : 0,
  duration: Duration(milliseconds: 200),
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(25),
    color: isDarkMode ? const Color(0xff2e3239) : backgroundColor, // set background color based on dark mode
    boxShadow:[
      BoxShadow(
        blurRadius: blur,
        offset: -distance,
        color: isDarkMode ? const Color(0xff35393f) : Colors.white, // set shadow color based on dark mode
      ),
      BoxShadow(
        blurRadius: blur,
        offset: distance,
        color: isDarkMode ? const Color(0xff23262a) : Colors.grey, // set shadow color based on dark mode
      )
    ]
  ),
),

Let’s Fix the Shadow Problem

In the previous code, we were using two box shadows to create the shadow effect on the button. But when we rotate the button, the shadow also rotates, which may not look visually appealing. So, to solve this problem, we will use different shadow offsets for when the button is clicked or not.

We have created two different shadow offsets: distance and distance2. distance is the shadow offset when the button is not clicked, and distance2 is the shadow offset when the button is clicked. We use a ternary operator to determine which shadow offset to use based on whether the button is clicked or not.

Overall, this gives us a nice shadow effect that remains consistent even when the button is rotated.

boxShadow:[
    BoxShadow(
        blurRadius: blur,
        offset: isClicked ? distance2 : -distance,
        color: isDarkMode ? const Color(0xff35393f) : Colors.white,
    ),
    BoxShadow(
        blurRadius: blur,
        offset: isClicked ? -distance2 : distance,
        color: isDarkMode ? const Color(0xff23262a) : Colors.grey,
    )
]
           

Here, the blurRadius is the amount of blur applied to the shadow, offset is the distance the shadow is offset from the button, and color is the color of the shadow.

We use the same isClicked ternary operator to determine which color to use for the shadows based on whether the app is in dark mode or not.

Output

As you have implemented the code and run it, you should see a button with the animated menu icon. When you click on the button, the icon rotates and expands the container, which covers the whole screen, displaying some dummy text. When you click the button again, the container shrinks and the icon rotates back to its original position. The button also changes color to reflect the dark or light mode based on the device setting. Overall, the output should show a simple but effective implementation of the Neumorphism style in a Flutter app.

the output of implementing the Neumorphism Animation button

Full Source Code

import 'package:flutter/material.dart';


void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      title: 'Stepper Widget- SiteJuggler',
      home: const NeumorphismAnimatedButton(),

    );
  }
}

class NeumorphismAnimatedButton extends StatefulWidget {
  const NeumorphismAnimatedButton({Key? key}) : super(key: key);

  @override
  State<NeumorphismAnimatedButton> createState() => _NeumorphismAnimatedButtonState();
}

class _NeumorphismAnimatedButtonState extends State<NeumorphismAnimatedButton>
    with TickerProviderStateMixin { ///new

  late AnimationController _controller;///new
  double turns = 0.0;
  bool isClicked = false;
  bool isDarkMode = true;
  Color customWhiteColor = Colors.white60;
  Color customBlackColor = Colors.black54;

  void _toggleTheme() {
    setState(() {
      isDarkMode = !isDarkMode;
    });
  }

  @override
  void initState() {
    _controller = AnimationController(
        duration: const Duration(milliseconds: 800),
        vsync:this
    );
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    const backgroundColor =  Colors.white70;
    Offset distance = const Offset(30,30);
    Offset distance2 = const Offset(-30,30);
    double blur = 40.0;

    return Scaffold(
     backgroundColor: isDarkMode?const Color(0xff2e3239):backgroundColor,
      appBar: AppBar(
        title:  Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset("assets/images/sitejuggler.png",
            height: 50,width: 50,),
            Text("SiteJuggler"),
          ],
        ),
        actions: [
          Switch(
            value: isDarkMode,
            onChanged: (value) {
              _toggleTheme();
            },
            activeColor: Colors.white,
            inactiveThumbColor: Colors.white,
          ),
        ],
      ),
      body: Center(
        child: Stack(
          children:[
            Padding(
              padding: const EdgeInsets.all(90.0),
              child: AnimatedContainer(
                height: isClicked?MediaQuery.of(context).size.height:0,
                  width: isClicked?MediaQuery.of(context).size.width:0,
                  //color: customBlackColor,
                  duration: Duration(milliseconds: 200),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(25),
                    color: isDarkMode?const Color(0xff2e3239):backgroundColor,
                    boxShadow:[
                      BoxShadow(
                        blurRadius: blur,
                        offset: -distance,
                        color: isDarkMode?const Color(0xff35393f):Colors.white,
                      ),
                      BoxShadow(
                        blurRadius: blur,
                        offset: distance,
                        color: isDarkMode?const Color(0xff23262a):Colors.grey,
                      )
                    ]
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: AnimatedContainer(
                height: isClicked?50:100,
                width: isClicked?50:100,
                duration: Duration(milliseconds: 200),
                child: AnimatedRotation(
                  curve: Curves.easeOutExpo, //starts fast and then get slow at end
                  turns: turns,
                  duration: const Duration(seconds: 1),
                  child: GestureDetector(
                    onTap: (){
                      if(isClicked){
                        setState(() {
                          turns -= 1/4; // this is one quarter of rotation
                          // turns = turns - 1/4;
                          _controller.reverse(); ///new
                        });
                      }
                      else{
                        setState(() {
                          turns += 1/4; // this is one quarter of rotation
                          // turns = turns + 1/4;
                          _controller.forward(); ///new
                        });
                      }
                      isClicked = !isClicked;
                    },
                    child: FittedBox(
                      child: Container(
                        decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(25),
                            color: isDarkMode?const Color(0xff2e3239):backgroundColor,
                            boxShadow:[
                              BoxShadow(
                                blurRadius: blur,
                                offset: isClicked?distance2:-distance,
                                color: isDarkMode?const Color(0xff35393f):Colors.white,
                              ),
                              BoxShadow(
                                blurRadius: blur,
                                offset: isClicked?-distance2:distance,
                                color: isDarkMode?const Color(0xff23262a):Colors.grey,
                              )
                            ]
                        ),
                        child: SizedBox(
                          height: 100,
                          width: 100,
                          child: Center(
                              child: AnimatedIcon(
                                icon: AnimatedIcons.menu_close,
                                progress: _controller,
                                size: 20,
                                 color:  isDarkMode? Colors.white:Colors.black,
                              )
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Conclusion

In this article, we learned how to create a beautiful Neumorphic Animation menu button in Flutter. We started by explaining what Neumorphism is and how it can be used in UI design. Then, we walked through the code step by step, adding a menu icon with animation and implementing a Neumorphic effect. We also added an expandable container that appears when the button is clicked. Finally, we solved the issue of the shadow turning along with the button. By following this tutorial, you should now have a good understanding of how to create Neumorphic UI elements in Flutter.

Previous Post

Neumorphism Button: How to Make a 3D Button in Flutter Complete Guide

Next Post

Global State Management using React Context API Complete Guide

Hemant Singh

Hemant Singh

Software Developer | Content Writer | Singer | Artist

Next Post
Global State Management using React Context API

Global State Management using React Context API Complete Guide

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You might also like

Debugging Flutter widgets

Debugging Flutter Widgets Like a Pro Complete Guide

May 14, 2023
rewards detail screen ui in flutter

How to Make Reward Detail Screen UI in Flutter Like Google Pay Complete Guide

May 14, 2023
Reward Screen UI in Flutter

How to Make Reward Screen UI in Flutter Like Google Pay Complete Guide

May 16, 2023
Discover the Top 10 Essential npm Scripts for React Development

Discover the Top 10 Essential npm Scripts for React Development

May 13, 2023
how to connect domain or subdomain with linux server using Cloudflare

how to connect domain or subdomain with linux server using Cloudflare

May 13, 2023
The 60-30-10 Rule in Flutter UI design

How to Implement the 60-30-10 Rule in Flutter UI Design Complete Guide

May 15, 2023
  • Privacy Policy
  • Terms of Use
  • About Us
  • Advertisement
  • Disclaimer
  • Contact Us

© 2023 Site Jugglers - Premium tutorial website for coding geeks.

No Result
View All Result
  • React
  • Flutter

© 2023 Site Jugglers - Premium tutorial website for coding geeks.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In