fullscreen: fixed overlay animation

This commit is contained in:
Thibault Deckers 2019-08-04 00:13:38 +09:00
parent 51372d7b26
commit 49a28c6d09
3 changed files with 77 additions and 34 deletions

View file

@ -366,6 +366,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
changePosition(notification);
return false;
},
child: Stack(
children: <Widget>[

View file

@ -5,6 +5,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/fullscreen/info_page.dart';
import 'package:aves/widgets/fullscreen/overlay.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
@ -27,9 +28,11 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
bool _isInitialScale = true;
int _currentHorizontalPage, _currentVerticalPage = 0;
PageController _horizontalPager, _verticalPager;
ValueNotifier<bool> _overlayVisible = ValueNotifier(false);
ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
AnimationController _overlayAnimationController;
Animation<Offset> _topOverlayOffset, _bottomOverlayOffset;
Animation<double> _topOverlayScale;
Animation<Offset> _bottomOverlayOffset;
EdgeInsets _frozenViewInsets, _frozenViewPadding;
List<ImageEntry> get entries => widget.entries;
@ -41,12 +44,20 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
_verticalPager = PageController(initialPage: _currentVerticalPage);
_overlayAnimationController = AnimationController(
duration: Duration(milliseconds: 250),
duration: Duration(milliseconds: 300),
vsync: this,
);
_topOverlayOffset = Tween(begin: Offset(0, 0), end: Offset(0, -1)).animate(CurvedAnimation(parent: _overlayAnimationController, curve: Curves.easeOutQuart, reverseCurve: Curves.easeInQuart));
_bottomOverlayOffset = Tween(begin: Offset(0, 0), end: Offset(0, 1)).animate(CurvedAnimation(parent: _overlayAnimationController, curve: Curves.easeOutQuart, reverseCurve: Curves.easeInQuart));
_topOverlayScale = CurvedAnimation(parent: _overlayAnimationController, curve: Curves.easeOutQuart, reverseCurve: Curves.easeInQuart);
_bottomOverlayOffset = Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(CurvedAnimation(parent: _overlayAnimationController, curve: Curves.easeOutQuart, reverseCurve: Curves.easeInQuart));
_overlayVisible.addListener(onOverlayVisibleChange);
initOverlay();
}
initOverlay() async {
// wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete
await Future.delayed(Duration(milliseconds: 300));
onOverlayVisibleChange();
}
@override
@ -70,11 +81,7 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
ImagePage(
entries: entries,
pageController: _horizontalPager,
onTap: () {
final visible = !_overlayVisible.value;
_overlayVisible.value = visible;
SystemChrome.setEnabledSystemUIOverlays(visible ? []: SystemUiOverlay.values);
},
onTap: () => _overlayVisible.value = !_overlayVisible.value,
onPageChanged: (page) => setState(() => _currentHorizontalPage = page),
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
),
@ -96,12 +103,12 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
],
),
if (_currentHorizontalPage != null && _currentVerticalPage == 0) ...[
SlideTransition(
position: _topOverlayOffset,
child: FullscreenTopOverlay(
FullscreenTopOverlay(
entries: entries,
index: _currentHorizontalPage,
),
scale: _topOverlayScale,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
),
Positioned(
bottom: 0,
@ -110,6 +117,8 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
child: FullscreenBottomOverlay(
entries: entries,
index: _currentHorizontalPage,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
),
),
)
@ -148,11 +157,19 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
);
}
onOverlayVisibleChange() {
if (_overlayVisible.value)
onOverlayVisibleChange() async {
if (_overlayVisible.value) {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
_overlayAnimationController.forward();
else
_overlayAnimationController.reverse();
} else {
final mq = MediaQuery.of(context);
_frozenViewInsets = mq.viewInsets;
_frozenViewPadding = mq.viewPadding;
SystemChrome.setEnabledSystemUIOverlays([]);
await _overlayAnimationController.reverse();
_frozenViewInsets = null;
_frozenViewPadding = null;
}
}
}

View file

@ -13,23 +13,35 @@ const kOverlayBackground = Colors.black26;
class FullscreenTopOverlay extends StatelessWidget {
final List<ImageEntry> entries;
final int index;
final Animation<double> scale;
final EdgeInsets viewInsets, viewPadding;
ImageEntry get entry => entries[index];
const FullscreenTopOverlay({Key key, this.entries, this.index}) : super(key: key);
const FullscreenTopOverlay({
Key key,
this.entries,
this.index,
this.scale,
this.viewInsets,
this.viewPadding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
OverlayButton(
scale: scale,
child: BackButton(),
),
Spacer(),
OverlayButton(
scale: scale,
child: IconButton(
icon: Icon(Icons.share),
onPressed: share,
@ -52,8 +64,15 @@ class FullscreenTopOverlay extends StatelessWidget {
class FullscreenBottomOverlay extends StatefulWidget {
final List<ImageEntry> entries;
final int index;
final EdgeInsets viewInsets, viewPadding;
const FullscreenBottomOverlay({Key key, this.entries, this.index}) : super(key: key);
const FullscreenBottomOverlay({
Key key,
this.entries,
this.index,
this.viewInsets,
this.viewPadding,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _FullscreenBottomOverlayState();
@ -86,13 +105,15 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
Widget build(BuildContext context) {
final innerPadding = EdgeInsets.all(8.0);
final mediaQuery = MediaQuery.of(context);
final overlayContentMaxWidth = mediaQuery.size.width - mediaQuery.viewPadding.horizontal - innerPadding.horizontal;
final viewInsets = widget.viewInsets ?? mediaQuery.viewInsets;
final viewPadding = widget.viewPadding ?? mediaQuery.viewPadding;
final overlayContentMaxWidth = mediaQuery.size.width - viewPadding.horizontal - innerPadding.horizontal;
return BlurredRect(
child: Container(
color: kOverlayBackground,
child: IgnorePointer(
child: Padding(
padding: mediaQuery.viewInsets + mediaQuery.viewPadding.copyWith(top: 0),
padding: viewInsets + viewPadding.copyWith(top: 0),
child: Container(
padding: innerPadding,
child: FutureBuilder(
@ -187,13 +208,16 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
}
class OverlayButton extends StatelessWidget {
final Animation<double> scale;
final Widget child;
const OverlayButton({this.child});
const OverlayButton({Key key, this.scale, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlurredOval(
return ScaleTransition(
scale: scale,
child: BlurredOval(
child: Material(
type: MaterialType.circle,
color: kOverlayBackground,
@ -205,6 +229,7 @@ class OverlayButton extends StatelessWidget {
child: child,
),
),
),
);
}
}