mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-24 16:30:40 +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_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:lightmeter/screens/settings/settings_page_route_builder.dart';
|
||||
|
||||
import 'generated/l10n.dart';
|
||||
import 'models/photography_value.dart';
|
||||
import 'res/dimens.dart';
|
||||
import 'res/theme.dart';
|
||||
import 'screens/metering/metering_bloc.dart';
|
||||
import 'screens/metering/metering_screen.dart';
|
||||
import 'utils/stop_type_provider.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const Application());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -22,11 +53,11 @@ class MyApp extends StatelessWidget {
|
|||
child: BlocProvider(
|
||||
create: (context) => MeteringBloc(context.read<StopType>()),
|
||||
child: MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: lightColorScheme,
|
||||
),
|
||||
navigatorObservers: [_settingsRouteObserver],
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
|
@ -34,9 +65,49 @@ class MyApp extends StatelessWidget {
|
|||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
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 Duration durationS = Duration(milliseconds: 100);
|
||||
static const Duration durationSM = Duration(milliseconds: 150);
|
||||
static const Duration durationM = Duration(milliseconds: 200);
|
||||
static const Duration durationL = Duration(milliseconds: 300);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/bottom_controls/bottom_controls.dart';
|
||||
|
@ -11,19 +12,19 @@ import 'metering_event.dart';
|
|||
import 'metering_state.dart';
|
||||
|
||||
class MeteringScreen extends StatefulWidget {
|
||||
const MeteringScreen({super.key});
|
||||
final AnimationController animationController;
|
||||
|
||||
const MeteringScreen({required this.animationController, super.key});
|
||||
|
||||
@override
|
||||
State<MeteringScreen> createState() => _MeteringScreenState();
|
||||
}
|
||||
|
||||
class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStateMixin {
|
||||
class _MeteringScreenState extends State<MeteringScreen> {
|
||||
final _topBarKey = GlobalKey(debugLabel: 'TopBarKey');
|
||||
final _middleAreaKey = GlobalKey(debugLabel: 'MiddleAreaKey');
|
||||
final _bottomBarKey = GlobalKey(debugLabel: 'BottomBarKey');
|
||||
|
||||
late final AnimationController _animationController;
|
||||
|
||||
bool _secondBuild = false;
|
||||
late double _topBarHeight;
|
||||
late double _middleAreaHeight;
|
||||
|
@ -32,13 +33,6 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 0 - collapsed
|
||||
// 1 - expanded
|
||||
_animationController = AnimationController(
|
||||
value: 0,
|
||||
duration: Dimens.durationL,
|
||||
vsync: this,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_topBarHeight = _getHeight(_topBarKey);
|
||||
_middleAreaHeight = _getHeight(_middleAreaKey);
|
||||
|
@ -85,7 +79,7 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
|||
top: 0,
|
||||
right: 0,
|
||||
child: MeteringScreenAnimatedSurface.top(
|
||||
controller: _animationController,
|
||||
controller: widget.animationController,
|
||||
areaHeight: _topBarHeight,
|
||||
overflowSize: _middleAreaHeight / 2,
|
||||
child: _topBar(state),
|
||||
|
@ -97,7 +91,7 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
|||
right: 0,
|
||||
bottom: 0,
|
||||
child: MeteringScreenAnimatedSurface.bottom(
|
||||
controller: _animationController,
|
||||
controller: widget.animationController,
|
||||
areaHeight: _bottomBarHeight,
|
||||
overflowSize: _middleAreaHeight / 2,
|
||||
child: _bottomBar(),
|
||||
|
@ -127,22 +121,10 @@ class _MeteringScreenState extends State<MeteringScreen> with TickerProviderStat
|
|||
onSourceChanged: () {},
|
||||
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
|
||||
onSettings: () {
|
||||
// Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen()));
|
||||
_toggleAnimation();
|
||||
Navigator.push(context, SettingsPageRouteBuilder());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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