seong
Flutter - SideMenu 본문
1. SideMenu Tile만들기
//Side_Menu_Tile의 변수
final RiveAsset menu;
final VoidCallback press;
final ValueChanged<Artboard> riveOnInIt;
final bool isActive; // 선택된 아이템 활성화 유무 판단
//
Column(
children: [
const Padding(
padding: EdgeInsets.only(left: 24),
child: Divider(
color: Colors.white24,
height: 1,
),
),
ListTile(
onTap: press,
leading: SizedBox(
height: 34,
width: 34,
child: RiveAnimation.asset(
//menu는 Rive asset의 model
menu.src,
artboard: menu.artboard,
onInit: riveOnInIt,
),
),
title: Text(
menu.title,
style: const TextStyle(color: Colors.white),
),
),
],
);
2. menu가 클릭 되었는지 확인을 위해 SelectedMenu 선언
RiveAsset selectedMenu = sidebarMenus.first;
3. Side_Menu에서 Tile호출
sidebarMenus 는 List<RiveAsset>형태이다.
map으로 요소 하나씩 SideMenuTile에 맞게 대입 해준다.
...sidebarMenus.map(
(menu) => SideMenuTile(
menu: menu,
riveOnInIt: (artboard) {
// 1. controller
StateMachineController controller = RiveUtils.getRiveController(
artboard,
stateMachineName: menu.stateMachineName,
);
// 2. active 넘겨주기
menu.input = controller.findSMI("active") as SMIBool;
},
press: () {
// 3. Animate 활성화
menu.input!.change(true);
// 4. 일정 시간 이후 Animate 비활성화
Future.delayed(const Duration(seconds: 1), () {
menu.input!.change(false);
});
// 5. 선택 메뉴 넘겨주기.
setState(() {
selectedMenu = menu;
});
},
isActive: selectedMenu == menu,
),
),
4. 선택된 Tile색 입히기.
다양한 방법이 있겠지만, Stack으로 구현되었다.
width: isActive ? 288 : 0 // 메뉴가 선택되었다면 Container의 width가 288이 된다,
이때 AnimatedPositioned를 사용해줌으로써 색이 부드럽게 입혀진다.
결국 원리는
ListTile과 width,height만 똑같은 Color를 가진 Container를 만들고,
선택되었다면 ListTile에 Container를 입혀주는 원리이다.
Container가 ListTile의 Text, Icon등을 가리지 않는이유
- Stack은 위젯을 아래에서 부터 쌓아올린다.
- 마지막에 쌓인 위젯이 가장 위에 있기 때문에 Icon,Text는 가려지지 않는다.
Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
height: 56,
width: isActive ? 288 : 0, // SideMenuTile의 가로너비와 동일
left: 0,
child: Container(
decoration: const BoxDecoration(
color: Color(0xFF6792FF),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
),
ListTile(
onTap: press,
leading: SizedBox(
height: 34,
width: 34,
child: RiveAnimation.asset(
menu.src,
artboard: menu.artboard,
onInit: riveOnInIt,
),
),
title: Text(
menu.title,
style: const TextStyle(color: Colors.white),
),
),
],
),
전체 코드
side_menu.dart
import 'package:animation_v1/components/side_menu_tile.dart';
import 'package:animation_v1/model/rive_asset.dart';
import 'package:animation_v1/utils/rive_utils.dart';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'info_card.dart';
class SideMenu extends StatefulWidget {
const SideMenu({super.key});
@override
State<SideMenu> createState() => _SideMenuState();
}
class _SideMenuState extends State<SideMenu> {
RiveAsset selectedMenu = sidebarMenus.first;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: 288,
height: double.infinity,
color: const Color(0xFF17203A),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const InfoCard(
name: "HyeonSeongLee",
profession: "Mobile Developer",
),
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) => SideMenuTile(
menu: menu,
riveOnInIt: (artboard) {
// 1. controller
StateMachineController controller = RiveUtils.getRiveController(
artboard,
stateMachineName: menu.stateMachineName,
);
// 2. active 넘겨주기
menu.input = controller.findSMI("active") as SMIBool;
},
press: () {
// 3. Animate 활성화
menu.input!.change(true);
// 4. 일정 시간 이후 Animate 비활성화
Future.delayed(const Duration(seconds: 1), () {
menu.input!.change(false);
});
// 5. 선택 메뉴 넘겨주기.
setState(() {
selectedMenu = menu;
});
},
isActive: selectedMenu == menu,
),
),
Padding(
padding: const EdgeInsets.only(left: 24, top: 32, bottom: 16),
child: Text(
"History".toUpperCase(), // 대문자
style: Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.white70),
),
),
...sidebarMenus2.map(
(menu2) => SideMenuTile(
menu: menu2,
press: () {
menu2.input!.change(true);
Future.delayed(const Duration(microseconds: 300), () {
menu2.input!.change(false);
});
setState(() {
selectedMenu = menu2;
});
},
riveOnInIt: (artboard) {
StateMachineController controller = RiveUtils.getRiveController(
artboard,
stateMachineName: menu2.stateMachineName,
);
menu2.input = controller.findSMI("active") as SMIBool;
},
isActive: selectedMenu == menu2,
),
),
],
),
),
),
);
}
}
side_menu_tile.dart
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import '../model/rive_asset.dart';
class SideMenuTile extends StatelessWidget {
const SideMenuTile({
super.key,
required this.menu,
required this.press,
required this.riveOnInIt,
required this.isActive,
});
final RiveAsset menu;
final VoidCallback press;
final ValueChanged<Artboard> riveOnInIt;
final bool isActive;
@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),
height: 56,
width: isActive ? 288 : 0, // SideMenuTile의 가로너비와 동일\
left: 0,
child: Container(
decoration: const BoxDecoration(
color: Color(0xFF6792FF),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
),
ListTile(
onTap: press,
leading: SizedBox(
height: 34,
width: 34,
child: RiveAnimation.asset(
menu.src,
artboard: menu.artboard,
onInit: riveOnInIt,
),
),
title: Text(
menu.title,
style: const TextStyle(color: Colors.white),
),
),
],
),
],
);
}
}
'Flutter > Flutter' 카테고리의 다른 글
Weather앱 만들기 - 날씨 API연동 (GetX,MVVM) (0) | 2023.10.13 |
---|---|
Flutter - Pro Animate 3D 입체감 살리기 (0) | 2023.09.14 |
Flutter -RiveAnimation Icon (2) (0) | 2023.09.12 |
Flutter - showDialog를 화면 위에서 내려오게 하기 (0) | 2023.09.12 |
Rive 만들기 (0) | 2023.09.09 |