mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-25 00:40:39 +00:00
connected animation with navigation
This commit is contained in:
parent
8ef53c344e
commit
ce04dcf257
4 changed files with 122 additions and 31 deletions
|
@ -1,20 +1,51 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:lightmeter/screens/settings/settings_page_route_builder.dart';
|
||||||
|
|
||||||
import 'generated/l10n.dart';
|
import 'generated/l10n.dart';
|
||||||
import 'models/photography_value.dart';
|
import 'models/photography_value.dart';
|
||||||
|
import 'res/dimens.dart';
|
||||||
import 'res/theme.dart';
|
import 'res/theme.dart';
|
||||||
import 'screens/metering/metering_bloc.dart';
|
import 'screens/metering/metering_bloc.dart';
|
||||||
import 'screens/metering/metering_screen.dart';
|
import 'screens/metering/metering_screen.dart';
|
||||||
import 'utils/stop_type_provider.dart';
|
import 'utils/stop_type_provider.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const Application());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||||
const MyApp({super.key});
|
|
||||||
|
class Application extends StatefulWidget {
|
||||||
|
const Application({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Application> createState() => _ApplicationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ApplicationState extends State<Application> with TickerProviderStateMixin {
|
||||||
|
late final AnimationController _animationController;
|
||||||
|
late final _settingsRouteObserver = _SettingsRouteObserver(onPush: _onSettingsPush, onPop: _onSettingsPop);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 0 - collapsed
|
||||||
|
// 1 - expanded
|
||||||
|
_animationController = AnimationController(
|
||||||
|
value: 0,
|
||||||
|
duration: Dimens.durationM,
|
||||||
|
reverseDuration: Dimens.durationSM,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -22,11 +53,11 @@ class MyApp extends StatelessWidget {
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => MeteringBloc(context.read<StopType>()),
|
create: (context) => MeteringBloc(context.read<StopType>()),
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
title: 'Flutter Demo',
|
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: lightColorScheme,
|
colorScheme: lightColorScheme,
|
||||||
),
|
),
|
||||||
|
navigatorObservers: [_settingsRouteObserver],
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
S.delegate,
|
S.delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
@ -34,9 +65,49 @@ class MyApp extends StatelessWidget {
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: S.delegate.supportedLocales,
|
supportedLocales: S.delegate.supportedLocales,
|
||||||
home: const MeteringScreen(),
|
home: MeteringScreen(animationController: _animationController),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSettingsPush() {
|
||||||
|
if (!_animationController.isAnimating && _animationController.status != AnimationStatus.completed) {
|
||||||
|
_animationController.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSettingsPop() {
|
||||||
|
Future.delayed(Dimens.durationSM).then((_) {
|
||||||
|
if (!_animationController.isAnimating && _animationController.status != AnimationStatus.dismissed) {
|
||||||
|
_animationController.reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsRouteObserver extends RouteObserver<SettingsPageRouteBuilder> {
|
||||||
|
final VoidCallback onPush;
|
||||||
|
final VoidCallback onPop;
|
||||||
|
|
||||||
|
_SettingsRouteObserver({
|
||||||
|
required this.onPush,
|
||||||
|
required this.onPop,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
||||||
|
super.didPush(route, previousRoute);
|
||||||
|
if (route is SettingsPageRouteBuilder) {
|
||||||
|
onPush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
||||||
|
super.didPop(route, previousRoute);
|
||||||
|
if (previousRoute is PageRoute && route is SettingsPageRouteBuilder) {
|
||||||
|
onPop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Dimens {
|
||||||
static const double paddingM = 16;
|
static const double paddingM = 16;
|
||||||
|
|
||||||
static const Duration durationS = Duration(milliseconds: 100);
|
static const Duration durationS = Duration(milliseconds: 100);
|
||||||
|
static const Duration durationSM = Duration(milliseconds: 150);
|
||||||
static const Duration durationM = Duration(milliseconds: 200);
|
static const Duration durationM = Duration(milliseconds: 200);
|
||||||
static const Duration durationL = Duration(milliseconds: 300);
|
static const Duration durationL = Duration(milliseconds: 300);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:lightmeter/screens/settings/settings_page_route_builder.dart';
|
||||||
|
|
||||||
import 'components/animated_surface/animated_surface.dart';
|
import 'components/animated_surface/animated_surface.dart';
|
||||||
import 'components/bottom_controls/bottom_controls.dart';
|
import 'components/bottom_controls/bottom_controls.dart';
|
||||||
|
@ -11,19 +12,19 @@ import 'metering_event.dart';
|
||||||
import 'metering_state.dart';
|
import 'metering_state.dart';
|
||||||
|
|
||||||
class MeteringScreen extends StatefulWidget {
|
class MeteringScreen extends StatefulWidget {
|
||||||
const MeteringScreen({super.key});
|
final AnimationController animationController;
|
||||||
|
|
||||||
|
const MeteringScreen({required this.animationController, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MeteringScreen> createState() => _MeteringScreenState();
|
State<MeteringScreen> createState() => _MeteringScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStateMixin {
|
class _MeteringScreenState extends State<MeteringScreen> {
|
||||||
final _topBarKey = GlobalKey(debugLabel: 'TopBarKey');
|
final _topBarKey = GlobalKey(debugLabel: 'TopBarKey');
|
||||||
final _middleAreaKey = GlobalKey(debugLabel: 'MiddleAreaKey');
|
final _middleAreaKey = GlobalKey(debugLabel: 'MiddleAreaKey');
|
||||||
final _bottomBarKey = GlobalKey(debugLabel: 'BottomBarKey');
|
final _bottomBarKey = GlobalKey(debugLabel: 'BottomBarKey');
|
||||||
|
|
||||||
late final AnimationController _animationController;
|
|
||||||
|
|
||||||
bool _secondBuild = false;
|
bool _secondBuild = false;
|
||||||
late double _topBarHeight;
|
late double _topBarHeight;
|
||||||
late double _middleAreaHeight;
|
late double _middleAreaHeight;
|
||||||
|
@ -32,13 +33,6 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// 0 - collapsed
|
|
||||||
// 1 - expanded
|
|
||||||
_animationController = AnimationController(
|
|
||||||
value: 0,
|
|
||||||
duration: Dimens.durationL,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_topBarHeight = _getHeight(_topBarKey);
|
_topBarHeight = _getHeight(_topBarKey);
|
||||||
_middleAreaHeight = _getHeight(_middleAreaKey);
|
_middleAreaHeight = _getHeight(_middleAreaKey);
|
||||||
|
@ -85,7 +79,7 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: MeteringScreenAnimatedSurface.top(
|
child: MeteringScreenAnimatedSurface.top(
|
||||||
controller: _animationController,
|
controller: widget.animationController,
|
||||||
areaHeight: _topBarHeight,
|
areaHeight: _topBarHeight,
|
||||||
overflowSize: _middleAreaHeight / 2,
|
overflowSize: _middleAreaHeight / 2,
|
||||||
child: _topBar(state),
|
child: _topBar(state),
|
||||||
|
@ -97,7 +91,7 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: MeteringScreenAnimatedSurface.bottom(
|
child: MeteringScreenAnimatedSurface.bottom(
|
||||||
controller: _animationController,
|
controller: widget.animationController,
|
||||||
areaHeight: _bottomBarHeight,
|
areaHeight: _bottomBarHeight,
|
||||||
overflowSize: _middleAreaHeight / 2,
|
overflowSize: _middleAreaHeight / 2,
|
||||||
child: _bottomBar(),
|
child: _bottomBar(),
|
||||||
|
@ -127,22 +121,10 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
||||||
onSourceChanged: () {},
|
onSourceChanged: () {},
|
||||||
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
|
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
|
||||||
onSettings: () {
|
onSettings: () {
|
||||||
// Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen()));
|
Navigator.push(context, SettingsPageRouteBuilder());
|
||||||
_toggleAnimation();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _getHeight(GlobalKey key) => key.currentContext!.size!.height;
|
double _getHeight(GlobalKey key) => key.currentContext!.size!.height;
|
||||||
|
|
||||||
void _toggleAnimation() {
|
|
||||||
if (_animationController.isAnimating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_animationController.status != AnimationStatus.dismissed) {
|
|
||||||
_animationController.reverse();
|
|
||||||
} else if (_animationController.status != AnimationStatus.completed) {
|
|
||||||
_animationController.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
37
lib/screens/settings/settings_page_route_builder.dart
Normal file
37
lib/screens/settings/settings_page_route_builder.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:lightmeter/screens/settings/settings_screen.dart';
|
||||||
|
|
||||||
|
class SettingsPageRouteBuilder extends PageRouteBuilder<void> {
|
||||||
|
SettingsPageRouteBuilder()
|
||||||
|
: super(
|
||||||
|
transitionDuration:
|
||||||
|
Dimens.durationM + Dimens.durationS, // wait for `MeteringScreenAnimatedSurface`s to expand
|
||||||
|
reverseTransitionDuration: Dimens.durationS,
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) => const SettingsScreen(),
|
||||||
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
final didPop = !(animation.value != 0.0 && secondaryAnimation.value == 0.0);
|
||||||
|
final tween = Tween(begin: 0.0, end: 1.0);
|
||||||
|
late Interval interval;
|
||||||
|
if (didPop) {
|
||||||
|
interval = const Interval(
|
||||||
|
0,
|
||||||
|
1.0,
|
||||||
|
curve: Curves.linear,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
interval = Interval(
|
||||||
|
Dimens.durationM.inMilliseconds / (Dimens.durationM + Dimens.durationS).inMilliseconds,
|
||||||
|
1.0,
|
||||||
|
curve: Curves.linear,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final animatable = tween.chain(CurveTween(curve: interval));
|
||||||
|
return Opacity(
|
||||||
|
opacity: (didPop ? secondaryAnimation : animation).drive(animatable).value,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue