// πŸš€ λ§₯뢁 λŸ°μΉ˜νŒ¨λ“œμ²˜λŸΌ "μ•žμ—μ„œ ν™•λŒ€λ˜λ©° λ‚˜νƒ€λ‚˜κ³ , λ’€λŠ” λΈ”λŸ¬ μ²˜λ¦¬λ˜λŠ”" μ „ν™˜ 효과. // 기쑴의 쒌우 μŠ¬λΌμ΄λ“œ μ „ν™˜μ„ λŒ€μ²΄ν•œλ‹€. 두 κ°€μ§€ μ“°μž„μƒˆκ°€ μžˆλ‹€: // 1) launchpadPageRoute β€” 전체 ν™”λ©΄ 이동(λ‹€λ₯Έ κΈ°λŠ₯ 타일 μ§„μž…)용. μ „ν™˜ μ€‘μ—λ§Œ λΈ”λŸ¬κ°€ 보이고, // 화면이 λ‹€ 뜨면 μ›λž˜ ν™”λ©΄(λŒ€μ‹œλ³΄λ“œ)은 μ™„μ „νžˆ κ°€λ €μ§„λ‹€. // 2) showLaunchpadMenu β€” μ„€μ •/λ‘œκ·Έμ•„μ›ƒ 같은 μž‘μ€ 메뉴 μ˜€λ²„λ ˆμ΄μš©. 메뉴가 λ–  μžˆλŠ” λ™μ•ˆ // 뒀에 μžˆλŠ” λŒ€μ‹œλ³΄λ“œκ°€ 계속 λΏŒμ˜‡κ²Œ 보인닀. λ°”κΉ₯을 νƒ­ν•˜λ©΄ λ‹«νžŒλ‹€. import 'dart:ui'; import 'package:flutter/material.dart'; class LaunchpadPageRoute extends PageRouteBuilder { LaunchpadPageRoute({required WidgetBuilder builder}) : super( transitionDuration: const Duration(milliseconds: 320), reverseTransitionDuration: const Duration(milliseconds: 240), pageBuilder: (context, animation, secondaryAnimation) => builder(context), transitionsBuilder: (context, animation, secondaryAnimation, child) { final curved = CurvedAnimation( parent: animation, curve: Curves.easeOutCubic, reverseCurve: Curves.easeInCubic, ); return AnimatedBuilder( animation: curved, child: child, builder: (context, child) { final t = curved.value; return Stack( children: [ // 뒀에 있던 ν™”λ©΄(λŒ€μ‹œλ³΄λ“œ)을 μ „ν™˜ν•˜λŠ” λ™μ•ˆλ§Œ λΏŒμ˜‡κ²Œ 보여쀀닀. Positioned.fill( child: IgnorePointer( child: BackdropFilter( filter: ImageFilter.blur( sigmaX: 22 * t, sigmaY: 22 * t, ), child: Container( color: Colors.black.withValues(alpha: 0.12 * t), ), ), ), ), Opacity( opacity: t, child: Transform.scale( scale: 0.94 + 0.06 * t, child: child, ), ), ], ); }, ); }, ); } /// ν™”λ©΄ 전체 이동에 λŸ°μΉ˜νŒ¨λ“œ μ „ν™˜ 효과λ₯Ό μ μš©ν•œλ‹€. MaterialPageRoute λŒ€μ‹  이걸 μ“°λ©΄ 됨. Future pushLaunchpad(BuildContext context, WidgetBuilder builder) { return Navigator.push(context, LaunchpadPageRoute(builder: builder)); } /// μ„€μ •/λ‘œκ·Έμ•„μ›ƒ 같은 μž‘μ€ 메뉴λ₯Ό λŸ°μΉ˜νŒ¨λ“œ μŠ€νƒ€μΌ(ν™•λŒ€+νŽ˜μ΄λ“œ, 배경은 계속 λΈ”λŸ¬ μœ μ§€)둜 λ„μš΄λ‹€. /// λ°”κΉ₯을 νƒ­ν•˜λ©΄ λ‹«νžŒλ‹€. builderλŠ” ν™”λ©΄ 전체λ₯Ό μ±„μš°λŠ” Align λ“±μœΌλ‘œ 메뉴 μœ„μΉ˜λ₯Ό 직접 μž‘μ•„μ•Ό ν•œλ‹€. Future showLaunchpadMenu({ required BuildContext context, required WidgetBuilder builder, }) { return showGeneralDialog( context: context, barrierDismissible: true, barrierLabel: 'menu', barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 260), pageBuilder: (context, animation, secondaryAnimation) => builder(context), transitionBuilder: (context, animation, secondaryAnimation, child) { final curved = CurvedAnimation( parent: animation, curve: Curves.easeOutCubic, reverseCurve: Curves.easeInCubic, ); return AnimatedBuilder( animation: curved, child: child, builder: (context, child) { final t = curved.value; return Stack( children: [ // 메뉴가 λ–  μžˆλŠ” λ™μ•ˆ 계속 λΏŒμ˜‡κ²Œ β€” κΈ°λŠ₯ ν™”λ©΄μ΄λž‘ ν—·κ°ˆλ¦¬μ§€ μ•Šκ²Œ μ§„ν•˜κ²Œ. Positioned.fill( child: IgnorePointer( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 32 * t, sigmaY: 32 * t), child: Container( color: Colors.black.withValues(alpha: 0.28 * t), ), ), ), ), Opacity( opacity: t, child: Transform.scale( alignment: Alignment.topRight, scale: 0.85 + 0.15 * t, child: child, ), ), ], ); }, ); }, ); }