implemented MeteringScreenAnimatedSurface

This commit is contained in:
Vadim 2022-11-26 21:14:51 +03:00
parent 4696b7d2ee
commit 8ef53c344e
3 changed files with 220 additions and 89 deletions

View file

@ -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<BorderRadius?> _borderRadiusAnimation;
final Animation<double> _childOpacityAnimation;
final Animation<double> _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<double>(
begin: 1,
end: 0,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.0, 0.4, curve: Curves.linear),
),
),
_overflowHeightAnimation = Tween<double>(
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,
),
),
),
),
),
);
}
}

View file

@ -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;
}

View file

@ -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<MeteringScreen> createState() => _MeteringScreenState();
}
class _MeteringScreenState extends State<MeteringScreen> 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<MeteringBloc, MeteringState>(
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<MeteringBloc>().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<MeteringBloc>().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();
}
}
}