From 8ef53c344efe33eeac98c181ec7d94551a8e3d5f Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sat, 26 Nov 2022 21:14:51 +0300 Subject: [PATCH] implemented `MeteringScreenAnimatedSurface` --- .../animated_surface/animated_surface.dart | 101 ++++++++++++ .../components/topbar/topbar_shape.dart | 61 -------- lib/screens/metering/metering_screen.dart | 147 ++++++++++++++---- 3 files changed, 220 insertions(+), 89 deletions(-) create mode 100644 lib/screens/metering/components/animated_surface/animated_surface.dart delete mode 100644 lib/screens/metering/components/topbar/topbar_shape.dart diff --git a/lib/screens/metering/components/animated_surface/animated_surface.dart b/lib/screens/metering/components/animated_surface/animated_surface.dart new file mode 100644 index 0000000..7ca3970 --- /dev/null +++ b/lib/screens/metering/components/animated_surface/animated_surface.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/res/dimens.dart'; + +class MeteringScreenAnimatedSurface extends _AnimatedSurface { + MeteringScreenAnimatedSurface.top({ + required super.controller, + required super.areaHeight, + required super.overflowSize, + required super.child, + }) : super( + alignment: Alignment.topCenter, + borderRadiusBegin: const BorderRadius.only( + bottomLeft: Radius.circular(Dimens.borderRadiusL), + bottomRight: Radius.circular(Dimens.borderRadiusL), + ), + ); + + MeteringScreenAnimatedSurface.bottom({ + required super.controller, + required super.areaHeight, + required super.overflowSize, + required super.child, + }) : super( + alignment: Alignment.bottomCenter, + borderRadiusBegin: const BorderRadius.only( + topLeft: Radius.circular(Dimens.borderRadiusL), + topRight: Radius.circular(Dimens.borderRadiusL), + ), + ); +} + +class _AnimatedSurface extends StatelessWidget { + final AnimationController controller; + final Alignment alignment; + final double areaHeight; + final double overflowSize; + final Widget child; + + final Animation _borderRadiusAnimation; + final Animation _childOpacityAnimation; + final Animation _overflowHeightAnimation; + + _AnimatedSurface({ + required this.controller, + required this.alignment, + required BorderRadius borderRadiusBegin, + required this.areaHeight, + required this.overflowSize, + required this.child, + }) : _borderRadiusAnimation = BorderRadiusTween( + begin: borderRadiusBegin, + end: BorderRadius.zero, + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.4, 1.0, curve: Curves.linear), + ), + ), + _childOpacityAnimation = Tween( + begin: 1, + end: 0, + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.0, 0.4, curve: Curves.linear), + ), + ), + _overflowHeightAnimation = Tween( + begin: 0, + end: overflowSize, + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.0, 1.0, curve: Curves.linear), + ), + ); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + child: child, + builder: (context, child) => SizedBox( + height: areaHeight + _overflowHeightAnimation.value, + child: ClipRRect( + borderRadius: _borderRadiusAnimation.value, + child: ColoredBox( + color: Theme.of(context).colorScheme.surface, + child: Align( + alignment: alignment, + child: Opacity( + opacity: _childOpacityAnimation.value, + child: child, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/metering/components/topbar/topbar_shape.dart b/lib/screens/metering/components/topbar/topbar_shape.dart deleted file mode 100644 index b729d46..0000000 --- a/lib/screens/metering/components/topbar/topbar_shape.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/res/dimens.dart'; - -class TopBarShape extends CustomPainter { - final Color color; - final Size appendixSize; - - TopBarShape({ - required this.color, - required this.appendixSize, - }); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint()..color = color; - final path = Path(); - const circularRadius = Radius.circular(Dimens.borderRadiusL); - if (appendixSize.shortestSide == 0) { - path.addRRect( - RRect.fromLTRBAndCorners( - 0, - 0, - 0, - 0, - bottomLeft: circularRadius, - bottomRight: circularRadius, - ), - ); - } else { - path.moveTo(size.width, 0); - path.lineTo(size.width, size.height - Dimens.borderRadiusL); - path.arcToPoint( - Offset(size.width - Dimens.borderRadiusL, size.height), - radius: circularRadius, - ); - path.lineTo(size.width - appendixSize.width + Dimens.borderRadiusL, size.height); - path.arcToPoint( - Offset(size.width - appendixSize.width, size.height - Dimens.borderRadiusL), - radius: circularRadius, - ); - path.lineTo(size.width - appendixSize.width, size.height - appendixSize.height + Dimens.borderRadiusM); - path.arcToPoint( - Offset(size.width - appendixSize.width - Dimens.borderRadiusM, size.height - appendixSize.height), - radius: circularRadius, - clockwise: false, - ); - path.lineTo(Dimens.borderRadiusL, size.height - appendixSize.height); - - path.arcToPoint( - Offset(0, size.height - appendixSize.height - Dimens.borderRadiusL), - radius: circularRadius, - ); - path.lineTo(0, 0); - path.close(); - } - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => false; -} diff --git a/lib/screens/metering/metering_screen.dart b/lib/screens/metering/metering_screen.dart index 166f8bf..efe898c 100644 --- a/lib/screens/metering/metering_screen.dart +++ b/lib/screens/metering/metering_screen.dart @@ -1,57 +1,148 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/metering/metering_event.dart'; -import 'package:lightmeter/screens/settings/settings_screen.dart'; +import 'components/animated_surface/animated_surface.dart'; import 'components/bottom_controls/bottom_controls.dart'; import 'components/exposure_pairs_list/exposure_pairs_list.dart'; import 'components/topbar/topbar.dart'; import 'metering_bloc.dart'; +import 'metering_event.dart'; import 'metering_state.dart'; -class MeteringScreen extends StatelessWidget { +class MeteringScreen extends StatefulWidget { const MeteringScreen({super.key}); + @override + State createState() => _MeteringScreenState(); +} + +class _MeteringScreenState extends State with TickerProviderStateMixin { + 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; + late double _bottomBarHeight; + + @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); + _bottomBarHeight = _getHeight(_bottomBarKey); + setState(() { + _secondBuild = true; + }); + }); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).colorScheme.background, body: BlocBuilder( builder: (context, state) { - return Column( + return Stack( children: [ - MeteringTopBar( - fastest: state.fastest, - slowest: state.slowest, - ev: state.ev, - iso: state.iso, - nd: state.nd, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), - child: Row( - children: [ - Expanded( - flex: 2, - child: ExposurePairsList(state.exposurePairs), + Column( + children: [ + if (_secondBuild) SizedBox(height: _topBarHeight) else _topBar(state), + Expanded( + key: _middleAreaKey, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), + child: Row( + children: [ + Expanded( + flex: 2, + child: ExposurePairsList(state.exposurePairs), + ), + const SizedBox(width: Dimens.grid16), + const Spacer() + ], ), - const SizedBox(width: Dimens.grid16), - const Spacer() - ], + ), + ), + if (_secondBuild) SizedBox(height: _bottomBarHeight) else _bottomBar(), + ], + ), + if (_secondBuild) + Positioned( + left: 0, + top: 0, + right: 0, + child: MeteringScreenAnimatedSurface.top( + controller: _animationController, + areaHeight: _topBarHeight, + overflowSize: _middleAreaHeight / 2, + child: _topBar(state), + ), + ), + if (_secondBuild) + Positioned( + left: 0, + right: 0, + bottom: 0, + child: MeteringScreenAnimatedSurface.bottom( + controller: _animationController, + areaHeight: _bottomBarHeight, + overflowSize: _middleAreaHeight / 2, + child: _bottomBar(), ), ), - ), - MeteringBottomControls( - onSourceChanged: () {}, - onMeasure: () => context.read().add(const MeasureEvent()), - onSettings: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), - ), ], ); }, ), ); } + + Widget _topBar(MeteringState state) { + return MeteringTopBar( + key: _topBarKey, + fastest: state.fastest, + slowest: state.slowest, + ev: state.ev, + iso: state.iso, + nd: state.nd, + ); + } + + Widget _bottomBar() { + return MeteringBottomControls( + key: _bottomBarKey, + onSourceChanged: () {}, + onMeasure: () => context.read().add(const MeasureEvent()), + onSettings: () { + // Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())); + _toggleAnimation(); + }, + ); + } + + 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(); + } + } }