Add base interactive layout "rive" whit animation

This commit is contained in:
paoloGuagnano
2024-03-08 12:15:47 +01:00
parent 06336bd3d9
commit 2d5fba8897
58 changed files with 1946 additions and 133 deletions

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class AnimatedBar extends StatelessWidget {
const AnimatedBar({
Key? key,
required this.isActive,
}) : super(key: key);
final bool isActive;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
margin: const EdgeInsets.only(bottom: 2),
duration: const Duration(milliseconds: 200),
height: 4,
width: isActive ? 20 : 0,
decoration: const BoxDecoration(
color: Color(0xFF81B4FF),
borderRadius: BorderRadius.all(
Radius.circular(12),
)),
);
}
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import '../../../model/menu.dart';
import 'animated_bar.dart';
class BtmNavItem extends StatelessWidget {
const BtmNavItem(
{super.key,
required this.navBar,
required this.press,
required this.riveOnInit,
required this.selectedNav});
final Menu navBar;
final VoidCallback press;
final ValueChanged<Artboard> riveOnInit;
final Menu selectedNav;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: press,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedBar(isActive: selectedNav == navBar),
SizedBox(
height: 36,
width: 36,
child: Opacity(
opacity: selectedNav == navBar ? 1 : 0.5,
child: RiveAnimation.asset(
navBar.rive.src,
artboard: navBar.rive.artboard,
onInit: riveOnInit,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class InfoCard extends StatelessWidget {
const InfoCard({
Key? key,
required this.name,
required this.bio,
}) : super(key: key);
final String name, bio;
@override
Widget build(BuildContext context) {
return ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.white24,
child: Icon(
CupertinoIcons.person,
color: Colors.white,
),
),
title: Text(
name,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
bio,
style: const TextStyle(color: Colors.white70),
),
);
}
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class MenuBtn extends StatelessWidget {
const MenuBtn({super.key, required this.press, required this.riveOnInit});
final VoidCallback press;
final ValueChanged<Artboard> riveOnInit;
@override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onTap: press,
child: Container(
margin: const EdgeInsets.only(left: 12),
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: riveOnInit,
),
),
),
);
}
}

View File

@@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import '../../../model/menu.dart';
import '../../../utils/rive_utils.dart';
import 'info_card.dart';
import 'side_menu.dart';
class SideBar extends StatefulWidget {
const SideBar({super.key});
@override
State<SideBar> createState() => _SideBarState();
}
class _SideBarState extends State<SideBar> {
Menu selectedSideMenu = sidebarMenus.first;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
width: 288,
height: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFF17203A),
borderRadius: BorderRadius.all(
Radius.circular(30),
),
),
child: DefaultTextStyle(
style: const TextStyle(color: Colors.white),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const InfoCard(
name: "Abu Anwar",
bio: "YouTuber",
),
Padding(
padding: const EdgeInsets.only(left: 24, top: 32, bottom: 16),
child: Text(
"Browse".toUpperCase(),
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Colors.white70),
),
),
...sidebarMenus
.map((menu) => SideMenu(
menu: menu,
selectedMenu: selectedSideMenu,
press: () {
RiveUtils.chnageSMIBoolState(menu.rive.status!);
setState(() {
selectedSideMenu = menu;
});
},
riveOnInit: (artboard) {
menu.rive.status = RiveUtils.getRiveInput(artboard,
stateMachineName: menu.rive.stateMachineName);
},
))
.toList(),
Padding(
padding: const EdgeInsets.only(left: 24, top: 40, bottom: 16),
child: Text(
"History".toUpperCase(),
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Colors.white70),
),
),
...sidebarMenus2
.map((menu) => SideMenu(
menu: menu,
selectedMenu: selectedSideMenu,
press: () {
RiveUtils.chnageSMIBoolState(menu.rive.status!);
setState(() {
selectedSideMenu = menu;
});
},
riveOnInit: (artboard) {
menu.rive.status = RiveUtils.getRiveInput(artboard,
stateMachineName: menu.rive.stateMachineName);
},
))
.toList(),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import '../../../model/menu.dart';
class SideMenu extends StatelessWidget {
const SideMenu(
{super.key,
required this.menu,
required this.press,
required this.riveOnInit,
required this.selectedMenu});
final Menu menu;
final VoidCallback press;
final ValueChanged<Artboard> riveOnInit;
final Menu selectedMenu;
@override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(
padding: EdgeInsets.only(left: 24),
child: Divider(color: Colors.white24, height: 1),
),
Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.fastOutSlowIn,
width: selectedMenu == menu ? 288 : 0,
height: 56,
left: 0,
child: Container(
decoration: const BoxDecoration(
color: Color(0xFF6792FF),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
),
ListTile(
onTap: press,
leading: SizedBox(
height: 36,
width: 36,
child: RiveAnimation.asset(
menu.rive.src,
artboard: menu.rive.artboard,
onInit: riveOnInit,
),
),
title: Text(
menu.title,
style: const TextStyle(color: Colors.white),
),
),
],
),
],
);
}
}

View File

@@ -0,0 +1,181 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:motula_translate_app/constants.dart';
import 'package:motula_translate_app/screens/home/home_screen.dart';
import 'package:motula_translate_app/utils/rive_utils.dart';
import '../../model/menu.dart';
import 'components/btm_nav_item.dart';
import 'components/menu_btn.dart';
import 'components/side_bar.dart';
class EntryPoint extends StatefulWidget {
const EntryPoint({super.key});
@override
State<EntryPoint> createState() => _EntryPointState();
}
class _EntryPointState extends State<EntryPoint>
with SingleTickerProviderStateMixin {
bool isSideBarOpen = false;
Menu selectedBottonNav = bottomNavItems.first;
Menu selectedSideMenu = sidebarMenus.first;
late SMIBool isMenuOpenInput;
void updateSelectedBtmNav(Menu menu) {
if (selectedBottonNav != menu) {
setState(() {
selectedBottonNav = menu;
});
}
}
late AnimationController _animationController;
late Animation<double> scalAnimation;
late Animation<double> animation;
@override
void initState() {
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200))
..addListener(
() {
setState(() {});
},
);
scalAnimation = Tween<double>(begin: 1, end: 0.8).animate(CurvedAnimation(
parent: _animationController, curve: Curves.fastOutSlowIn));
animation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: _animationController, curve: Curves.fastOutSlowIn));
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
resizeToAvoidBottomInset: false,
backgroundColor: backgroundColor2,
body: Stack(
children: [
AnimatedPositioned(
width: 288,
height: MediaQuery.of(context).size.height,
duration: const Duration(milliseconds: 200),
curve: Curves.fastOutSlowIn,
left: isSideBarOpen ? 0 : -288,
top: 0,
child: const SideBar(),
),
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(
1 * animation.value - 30 * (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: HomePage(),
),
),
),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 200),
curve: Curves.fastOutSlowIn,
left: isSideBarOpen ? 220 : 0,
top: 16,
child: MenuBtn(
press: () {
isMenuOpenInput.value = !isMenuOpenInput.value;
if (_animationController.value == 0) {
_animationController.forward();
} else {
_animationController.reverse();
}
setState(
() {
isSideBarOpen = !isSideBarOpen;
},
);
},
riveOnInit: (artboard) {
final controller = StateMachineController.fromArtboard(
artboard, "State Machine");
artboard.addController(controller!);
isMenuOpenInput =
controller.findInput<bool>("isOpen") as SMIBool;
isMenuOpenInput.value = true;
},
),
),
],
),
bottomNavigationBar: Transform.translate(
offset: Offset(0, 100 * animation.value),
child: SafeArea(
child: Container(
padding:
const EdgeInsets.only(left: 12, top: 12, right: 12, bottom: 12),
margin: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: backgroundColor2.withOpacity(0.8),
borderRadius: const BorderRadius.all(Radius.circular(24)),
boxShadow: [
BoxShadow(
color: backgroundColor2.withOpacity(0.3),
offset: const Offset(0, 20),
blurRadius: 20,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
...List.generate(
bottomNavItems.length,
(index) {
Menu navBar = bottomNavItems[index];
return BtmNavItem(
navBar: navBar,
press: () {
RiveUtils.chnageSMIBoolState(navBar.rive.status!);
updateSelectedBtmNav(navBar);
},
riveOnInit: (artboard) {
navBar.rive.status = RiveUtils.getRiveInput(artboard,
stateMachineName: navBar.rive.stateMachineName);
},
selectedNav: selectedBottonNav,
);
},
),
],
),
),
),
),
);
}
}