9.2 KiB
Apple Fluid Design — примеры изменений
Контекст
Текущий UI уже iOS-вдохновлённый (белые карточки на сером фоне, цвет #007AFF, flat Material 3).
Задача — поднять его до уровня Apple Fluid (visionOS/iOS 16+ эстетика):
стеклянные поверхности, мягкие градиентные фоны, пружинные анимации, размытые навбары.
Ключевые файлы для изменений
client/lib/core/theme/app_colors.dart— цветовая системаclient/lib/core/theme/app_theme.dart— Material themeclient/lib/features/home/home_screen.dart— главный экранclient/lib/app.dart— корень приложения
1. Градиентный фон вместо плоского #F2F2F7
Текущее состояние: scaffoldBackgroundColor: AppColors.background — серый #F2F2F7.
Fluid-подход: мягкий меш-градиент с 2–3 цветовыми пятнами.
// Новый виджет-обёртка — заменяет Scaffold background
class FluidBackground extends StatelessWidget {
final Widget child;
const FluidBackground({required this.child, super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFEEF4FF), // холодный голубой
Color(0xFFF5F0FF), // лавандовый
Color(0xFFE8F8F2), // мятный
],
stops: [0.0, 0.55, 1.0],
),
),
child: child,
);
}
}
Применение в home_screen.dart: обернуть Scaffold в FluidBackground.
2. Стеклянные карточки (Glassmorphism)
Текущее состояние: Card с elevation: 0 и белым фоном.
Fluid-подход: BackdropFilter + ImageFilter.blur + полупрозрачный контейнер.
import 'dart:ui';
class GlassCard extends StatelessWidget {
final Widget child;
final EdgeInsets padding;
final double borderRadius;
const GlassCard({
required this.child,
this.padding = const EdgeInsets.all(16),
this.borderRadius = 20,
super.key,
});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(borderRadius),
border: Border.all(
color: Colors.white.withValues(alpha: 0.6),
width: 1.0,
),
boxShadow: [
BoxShadow(
color: const Color(0xFF007AFF).withValues(alpha: 0.06),
blurRadius: 24,
offset: const Offset(0, 8),
),
],
),
padding: padding,
child: child,
),
),
);
}
}
Применение: заменить Card(...) на GlassCard(child: ...) для _CaloriesCard, _MacrosRow.
3. Frosted-glass навигационная панель
Текущее состояние: белый BottomNavigationBar с elevation: 0.
Fluid-подход: размытая нижняя панель, как iOS Home indicator area.
class FluidBottomNavBar extends StatelessWidget {
final int currentIndex;
final ValueChanged<int> onTap;
const FluidBottomNavBar({
required this.currentIndex,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.80),
border: Border(
top: BorderSide(
color: Colors.white.withValues(alpha: 0.5),
width: 0.5,
),
),
),
child: BottomNavigationBar(
currentIndex: currentIndex,
onTap: onTap,
backgroundColor: Colors.transparent,
elevation: 0,
// ... items
),
),
),
);
}
}
4. Rounded шрифт — DM Sans / Plus Jakarta Sans
Текущее состояние: GoogleFonts.roboto() — нейтральный, не «тёплый».
Fluid-подход: GoogleFonts.dmSans() или GoogleFonts.plusJakartaSans() — округлые буквы, ближе к SF Pro Rounded.
// app_theme.dart
fontFamily: GoogleFonts.dmSans().fontFamily,
Изменение в одну строку — затрагивает весь UI сразу.
5. Цветные «blob»-тени на карточках вместо нейтральных
Текущее состояние: тени отсутствуют (elevation: 0).
Fluid-подход: мягкая цветная тень #007AFF с малой opacity.
// Вместо CardTheme используем обёртку с boxShadow
BoxDecoration fluidCardDecoration = BoxDecoration(
color: Colors.white.withValues(alpha: 0.80),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: const Color(0xFF007AFF).withValues(alpha: 0.08),
blurRadius: 32,
spreadRadius: 0,
offset: const Offset(0, 12),
),
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
);
6. Пружинные анимации (Spring Physics)
Текущее состояние: стандартный AnimatedContainer с Duration(milliseconds: 150).
Fluid-подход: SpringDescription с bounce, как в UIKit.
// Пример для DateSelector — при выборе даты
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.8, end: 1.0),
duration: const Duration(milliseconds: 400),
curve: Curves.easeOutBack, // ← даёт эффект bounce
builder: (context, scaleValue, child) =>
Transform.scale(scale: scaleValue, child: child),
child: selectedDatePill,
)
7. Frosted Bottom Sheet
Текущее состояние: белый bottom sheet с borderRadius: 12.
Fluid-подход: стеклянный sheet с blur из контента под ним.
// В showModalBottomSheet:
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent, // ← ключевое
builder: (sheetContext) => ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.88),
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
border: Border(
top: BorderSide(
color: Colors.white.withValues(alpha: 0.7),
width: 1,
),
),
),
child: FoodSearchSheet(...),
),
),
),
);
8. Скругления: 12 → 20pt
Текущее состояние: BorderRadius.circular(12) повсюду.
Fluid-подход: 20pt для карточек, 28pt для bottom sheets, 14pt для полей ввода.
// app_theme.dart
cardTheme: CardThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), // было 12
),
),
bottomSheetTheme: const BottomSheetThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)), // было 12
),
),
Итог: приоритет изменений
| Приоритет | Изменение | Файл | Эффект |
|---|---|---|---|
| 1 | Шрифт DM Sans | app_theme.dart |
Весь UI сразу |
| 2 | Скругления 20/28pt | app_theme.dart |
Весь UI сразу |
| 3 | Градиентный фон | home_screen.dart + новый виджет |
Главный экран |
| 4 | GlassCard |
новый файл glass_card.dart |
Карточки калорий/макро |
| 5 | Цветные тени | точечно в карточках | Глубина |
| 6 | Frosted навбар | app.dart |
Всё приложение |
| 7 | Frosted bottom sheet | food_search_sheet.dart |
Поиск еды |
| 8 | Spring-анимации | home_screen.dart |
DateSelector |
Пункты 1–2 — изменения в одну строку с максимальным эффектом.
Пункты 3–5 — создание GlassCard и замена Card на него в 2–3 местах.
Пункты 6–8 — более глубокие изменения навигации и transition.