Flutter Create Animated 3D SideMenu with Rive - DevhubSpot

Flutter Android IOS animation App 3D

In this article, I am going to introduce a side menu drawer using Rive animation. Along with learning the awesome 3D side menu drawer using rive animation UI implementation in Flutter, we will also learn how its coding workflows and structures work.

What is Rive?

Rive is a very useful animation tool that can create beautiful animations and we can add these to our Application. In Flutter, we can add animations by writing so many lines of code but this is not a good practice for a developer. Instead of writing lines of code to create animation, we can create one using this powerful Rive animation tool. Please read all the below points in sequence to understand the topic clearly. 

How to Create a New Future Project

First, we need to create a new Flutter project. For that, make sure that you've installed the Flutter SDK and other Flutter app development-related requirements.

If everything is properly set up, then in order to create a project we can simply run the following command in our desired local directory:

flutter create flutter-sidemenu-rive

After we've set up the project, we can navigate inside the project directory and execute the following command in the terminal to run the project in either an available emulator or an actual device:

flutter run

First, we need to make some simple configurations to the default boilerplate code in the main.dart file. We'll remove some default code and add the simple MaterialApp pointing to the remove Container and Call NavigationPoint page for now:

import 'package:flutter/material.dart';
import 'package:riveanimation/navigationPoint.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(
      debugShowCheckedModeBanner: false,
      title: 'Rive Drawer and bottom tabs devhubspot',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: NavigationPoint()
    );
  }
}


After Creating the utils folder inner lib folder and create a file riveutils.dart and add the following code

import 'package:rive/rive.dart';

class RiveUtils {
  static StateMachineController getRiveController(Artboard artboard,
      {stateMachineName = "State Machine 1"}) {
    StateMachineController? controller =
        StateMachineController.fromArtboard(artboard, stateMachineName);
    artboard.addController(controller!);
    return controller;
  }
}


After Creating the models folder inner lib folder and create a file riveassets.dart and add the following code

import 'package:rive/rive.dart';

class RiveAsset {
  final String artboard, stateMachineName, title, src;
  late SMIBool? input;

  RiveAsset(this.src,
      {required this.artboard,
      required this.stateMachineName,
      required this.title,
      this.input});

  set setInput(SMIBool status) {
    input = status;
  }
}

List<RiveAsset> sideMenus = [
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "HOME",
    stateMachineName: "HOME_Interactivity",
    title: "Home",
  ),
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "SEARCH",
    stateMachineName: "SEARCH_Interactivity",
    title: "Search",
  ),
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "LIKE/STAR",
    stateMachineName: "STAR_Interactivity",
    title: "Favorites",
  ),
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "CHAT",
    stateMachineName: "CHAT_Interactivity",
    title: "Help",
  ),
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "TIMER",
    stateMachineName: "TIMER_Interactivity",
    title: "History",
  ),
  RiveAsset(
    "assets/RiveAssets/icons.riv",
    artboard: "BELL",
    stateMachineName: "BELL_Interactivity",
    title: "Notification",
  ),
];

After Creating the sidemenu folder inner lib folder and creating a file sidemenu.dart widget and add the following code

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:riveanimation/models/riveassets.dart';
import 'package:riveanimation/utils/riveutils.dart';

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

  @override
  State<SideMenu> createState() => _SideMenuState();
}

class _SideMenuState extends State<SideMenu> {
  RiveAsset selectedMenu = sideMenus.first;
  @override
  Widget build(BuildContext context) {
    StateMachineController controller;
    return Scaffold(
      body: Container(
        width: 288,
        height: double.infinity,
        color: Color(0xff17203a),
        child: SafeArea(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ListTile(
                leading: const CircleAvatar(
                  backgroundColor: Colors.white24,
                  child: Icon(Icons.person, color: Colors.white),
                ),
                title: Text("DevHubSpot", style: TextStyle(color: Colors.white)),
                subtitle: Text("Blogger", style: TextStyle(color: Colors.white),),
              ),

              Padding(
                padding: EdgeInsets.only(left: 22, top:30, bottom: 14),
                child: Text("Browser", style: TextStyle(color: Colors.white),),
              ),

              ...sideMenus.map(
                (menu) => Column(
                  children: [
                    Padding(
                      padding: EdgeInsets.only(left: 22),
                      child: Divider(
                        color: Colors.white24,
                        height: 1,
                      ),
                    ),

                    Stack(
                      children: [
                        AnimatedPositioned(
                          duration: Duration(milliseconds: 300),
                          curve: Curves.fastOutSlowIn,
                          height: 56,
                          width: selectedMenu == menu ? 288:0,
                          left: 0,
                          child: Container(
                            decoration: BoxDecoration(
                              color: Color(0xff6792ff),
                              borderRadius: BorderRadius.all(Radius.circular(10))
                            ),
                          ),
                        ),
                        ListTile(
                          onTap: () {
                            print(menu);
                            menu.input?.change(true);
                            setState(() {
                              selectedMenu = menu;
                            });
                            Future.delayed(const Duration(seconds: 1), () {
                             menu.input?.change(false);
                            });
                           
                          },
                          leading: SizedBox(
                            height: 35, width: 35,
                            child: RiveAnimation.asset(
                              menu.src,
                              artboard: menu.artboard,
                              onInit: (artboard) {
                                StateMachineController controller = RiveUtils.getRiveController(artboard, stateMachineName: menu.stateMachineName);
                                menu.input = controller.findSMI("active") as SMIBool;
                              },
                            ),
                          ),
                          title : Text(menu.title, style: TextStyle(color: Colors.white),)
                        )
                      ],
                    )
                  ],
                )
              )
            ],
          ),
        ),
      ),
    );
  }
}


After Creating the models folder inner lib folder and create a file instructor.dart and add the following code

import 'package:flutter/material.dart';

class Instructor{
  final String title, description, iconSrc;
  final Color bgColor;

  Instructor({required this.title, this.description = "Build animate flutter app from scratch", this.iconSrc = "assets/icons/2.jpg", this.bgColor = const Color(0xff7553F6)});

}

List<Instructor> instructors = [
  Instructor(title: "Flutter animation app"),
  Instructor(title: "Devhubspot Flutter tutorial", bgColor:Colors.blueAccent),
  Instructor(title: "Devhubspot Flutter rive animation", bgColor:Colors.deepPurpleAccent),
  Instructor(title: "Devhubspot Flutter animation", bgColor:Colors.pinkAccent),
];

After Creating the screens folder inner lib folder and create a file home.dart and add the following code

import 'package:flutter/material.dart';
import 'package:riveanimation/models/instructor.dart';

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

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: SafeArea(
        bottom: false,
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(height: 40,),
              Padding(padding: EdgeInsets.only(top: 40, bottom: 10, left: 15),
                child: Text("Instructor", style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600)),
              ),
              SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: [
                    ...instructors.map((e) => Padding(
                      padding: EdgeInsets.all(10),
                      child: Container(
                        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 22),
                        height: 200,
                        width: 260,
                        decoration: BoxDecoration(
                          color: e.bgColor,
                          borderRadius: BorderRadius.all(Radius.circular(20))
                        ),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Expanded(child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(e.title, style: TextStyle(color:Colors.white, fontWeight:  FontWeight.w600)),
                                Padding(padding: EdgeInsets.only(top: 12, bottom: 8), 
                                child: Text(e.description, style: TextStyle(color: Colors.white70))),
                              ],
                            ))
                          ],
                        ),
                      ),
                    )).toList(),
                  ],
                ),
              ),
               Padding(padding: EdgeInsets.only(top: 40, bottom: 10, left: 15),
                  child: Text("Recent Instructor", style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600)),
                ),

                Container(
                  child: Column(
                      children: [
                      ...instructors.map((e) => Padding(
                        padding: EdgeInsets.all(10),
                        child: Container(
                          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 22),
                          height: 200,
                          width: double.infinity,
                          decoration: BoxDecoration(
                            color: e.bgColor,
                            borderRadius: BorderRadius.all(Radius.circular(20))
                          ),
                          child: Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Expanded(child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(e.title, style: TextStyle(color:Colors.white, fontWeight:  FontWeight.w600)),
                                  Padding(padding: EdgeInsets.only(top: 12, bottom: 8), 
                                  child: Text(e.description, style: TextStyle(color: Colors.white70))),
                                ],
                              ))
                            ],
                          ),
                        ),
                      )).toList(),
                    ],
                  ),
                )
            ],
          ),
        ),
      ),
    );
  }
}

After Creating NavigationPoint.dart file in the inner lib folder 

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:riveanimation/screens/home.dart';
import 'package:riveanimation/sidemenu/sidemenu.dart';
import 'package:rive/rive.dart';
import 'package:riveanimation/utils/riveutils.dart';

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

  @override
  State<NavigationPoint> createState() => _NavigationPointState();
}

class _NavigationPointState extends State<NavigationPoint> with SingleTickerProviderStateMixin{

  late AnimationController _animationController;
  late Animation<double> animation;
  late Animation<double> scalAnimation;

  late SMIBool isSideBarClosed;
  bool isSideMenuClosed = true;


  @override
  void initState() {
    // TODO: implement initState

    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
    )..addListener(() {
        setState(() {});
    });

    animation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(parent: _animationController
      , curve: Curves.fastOutSlowIn),
    );

   scalAnimation = Tween<double>(begin: 1, end: 0.8).animate(
      CurvedAnimation(parent: _animationController
      , curve: Curves.fastOutSlowIn),
    );

    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      extendBody: true,
       body: Stack(
        // It's time to add the SideMenu
        children: [
          // It shows nothing
          // because now it's under the HomeScreen
          AnimatedPositioned(
            duration: const Duration(milliseconds: 200),
            curve: Curves.fastOutSlowIn,
            width: 288,
            left: isSideMenuClosed ? -288 : 0,
            height: MediaQuery.of(context).size.height,
            child: const SideMenu(),
          ),
          Transform(
            alignment: Alignment.center,
            transform: Matrix4.identity()
              ..setEntry(3, 2, 0.001)
              ..rotateY(animation.value - 40 * animation.value * pi / 180),
            child: Transform.translate(
              offset: Offset(animation.value * 265, 0),
              child: Transform.scale(
                scale: scalAnimation.value,
                child: const ClipRRect(
                  borderRadius: BorderRadius.all(Radius.circular(24)),
                  child: Home(),
                ),
              ),
            ),
          ),
          // As you can see it's an ANimated button
          AnimatedPositioned(
            duration: Duration(milliseconds: 200),
            curve: Curves.fastOutSlowIn,
            left: isSideMenuClosed ? 0 : 220,
            top: 16,
            child: SafeArea(
              child: GestureDetector(
                onTap: (){
                  isSideBarClosed.value = !isSideBarClosed.value;
                if (isSideMenuClosed) {
                  _animationController.forward();
                } else {
                  _animationController.reverse();
                }
                setState(() {
                  isSideMenuClosed = isSideBarClosed.value;
                });
                },
                child: Container(
                  margin: const EdgeInsets.only(left: 16),
                  height: 40,
                  width: 40,
                  decoration: const BoxDecoration(
                    color: Colors.white,
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black12,
                        offset: Offset(0, 3),
                        blurRadius: 8,
                      )
                    ],
                  ),
                  child: RiveAnimation.asset(
                        "assets/RiveAssets/menu_button.riv",
                        onInit:  (artboard) {
                    StateMachineController controller = RiveUtils.getRiveController(
                        artboard,
                        stateMachineName: "State Machine");
                    isSideBarClosed = controller.findSMI("isOpen") as SMIBool;
                    // Now it's easy to understand
                    isSideBarClosed.value = true;
                  },
                  ),
                ),
              ),
            )
          ),
        ],
      ),
    );
  }
}

Output:


Flutter Android IOS animation App 3D
Comments

AdBlock Detected!

Our website is made possible by displaying ads to our visitors. Please supporting us by whitelisting our website.