fullscreen: fixed overlay animation
This commit is contained in:
parent
51372d7b26
commit
49a28c6d09
3 changed files with 77 additions and 34 deletions
|
@ -366,6 +366,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (ScrollNotification notification) {
|
onNotification: (ScrollNotification notification) {
|
||||||
changePosition(notification);
|
changePosition(notification);
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info_page.dart';
|
import 'package:aves/widgets/fullscreen/info_page.dart';
|
||||||
import 'package:aves/widgets/fullscreen/overlay.dart';
|
import 'package:aves/widgets/fullscreen/overlay.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
@ -27,9 +28,11 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
bool _isInitialScale = true;
|
bool _isInitialScale = true;
|
||||||
int _currentHorizontalPage, _currentVerticalPage = 0;
|
int _currentHorizontalPage, _currentVerticalPage = 0;
|
||||||
PageController _horizontalPager, _verticalPager;
|
PageController _horizontalPager, _verticalPager;
|
||||||
ValueNotifier<bool> _overlayVisible = ValueNotifier(false);
|
ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
AnimationController _overlayAnimationController;
|
AnimationController _overlayAnimationController;
|
||||||
Animation<Offset> _topOverlayOffset, _bottomOverlayOffset;
|
Animation<double> _topOverlayScale;
|
||||||
|
Animation<Offset> _bottomOverlayOffset;
|
||||||
|
EdgeInsets _frozenViewInsets, _frozenViewPadding;
|
||||||
|
|
||||||
List<ImageEntry> get entries => widget.entries;
|
List<ImageEntry> get entries => widget.entries;
|
||||||
|
|
||||||
|
@ -41,12 +44,20 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
|
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
|
||||||
_verticalPager = PageController(initialPage: _currentVerticalPage);
|
_verticalPager = PageController(initialPage: _currentVerticalPage);
|
||||||
_overlayAnimationController = AnimationController(
|
_overlayAnimationController = AnimationController(
|
||||||
duration: Duration(milliseconds: 250),
|
duration: Duration(milliseconds: 300),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
_topOverlayOffset = 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, 0), end: Offset(0, 1)).animate(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);
|
_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
|
@override
|
||||||
|
@ -70,11 +81,7 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
ImagePage(
|
ImagePage(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
pageController: _horizontalPager,
|
pageController: _horizontalPager,
|
||||||
onTap: () {
|
onTap: () => _overlayVisible.value = !_overlayVisible.value,
|
||||||
final visible = !_overlayVisible.value;
|
|
||||||
_overlayVisible.value = visible;
|
|
||||||
SystemChrome.setEnabledSystemUIOverlays(visible ? []: SystemUiOverlay.values);
|
|
||||||
},
|
|
||||||
onPageChanged: (page) => setState(() => _currentHorizontalPage = page),
|
onPageChanged: (page) => setState(() => _currentHorizontalPage = page),
|
||||||
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
|
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
|
||||||
),
|
),
|
||||||
|
@ -96,12 +103,12 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_currentHorizontalPage != null && _currentVerticalPage == 0) ...[
|
if (_currentHorizontalPage != null && _currentVerticalPage == 0) ...[
|
||||||
SlideTransition(
|
FullscreenTopOverlay(
|
||||||
position: _topOverlayOffset,
|
entries: entries,
|
||||||
child: FullscreenTopOverlay(
|
index: _currentHorizontalPage,
|
||||||
entries: entries,
|
scale: _topOverlayScale,
|
||||||
index: _currentHorizontalPage,
|
viewInsets: _frozenViewInsets,
|
||||||
),
|
viewPadding: _frozenViewPadding,
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
@ -110,6 +117,8 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
child: FullscreenBottomOverlay(
|
child: FullscreenBottomOverlay(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
index: _currentHorizontalPage,
|
index: _currentHorizontalPage,
|
||||||
|
viewInsets: _frozenViewInsets,
|
||||||
|
viewPadding: _frozenViewPadding,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -148,11 +157,19 @@ class FullscreenPageState extends State<FullscreenPage> with SingleTickerProvide
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOverlayVisibleChange() {
|
onOverlayVisibleChange() async {
|
||||||
if (_overlayVisible.value)
|
if (_overlayVisible.value) {
|
||||||
|
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
|
||||||
_overlayAnimationController.forward();
|
_overlayAnimationController.forward();
|
||||||
else
|
} else {
|
||||||
_overlayAnimationController.reverse();
|
final mq = MediaQuery.of(context);
|
||||||
|
_frozenViewInsets = mq.viewInsets;
|
||||||
|
_frozenViewPadding = mq.viewPadding;
|
||||||
|
SystemChrome.setEnabledSystemUIOverlays([]);
|
||||||
|
await _overlayAnimationController.reverse();
|
||||||
|
_frozenViewInsets = null;
|
||||||
|
_frozenViewPadding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,23 +13,35 @@ const kOverlayBackground = Colors.black26;
|
||||||
class FullscreenTopOverlay extends StatelessWidget {
|
class FullscreenTopOverlay extends StatelessWidget {
|
||||||
final List<ImageEntry> entries;
|
final List<ImageEntry> entries;
|
||||||
final int index;
|
final int index;
|
||||||
|
final Animation<double> scale;
|
||||||
|
final EdgeInsets viewInsets, viewPadding;
|
||||||
|
|
||||||
ImageEntry get entry => entries[index];
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
OverlayButton(
|
OverlayButton(
|
||||||
|
scale: scale,
|
||||||
child: BackButton(),
|
child: BackButton(),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
OverlayButton(
|
OverlayButton(
|
||||||
|
scale: scale,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(Icons.share),
|
icon: Icon(Icons.share),
|
||||||
onPressed: share,
|
onPressed: share,
|
||||||
|
@ -52,8 +64,15 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
class FullscreenBottomOverlay extends StatefulWidget {
|
class FullscreenBottomOverlay extends StatefulWidget {
|
||||||
final List<ImageEntry> entries;
|
final List<ImageEntry> entries;
|
||||||
final int index;
|
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
|
@override
|
||||||
State<StatefulWidget> createState() => _FullscreenBottomOverlayState();
|
State<StatefulWidget> createState() => _FullscreenBottomOverlayState();
|
||||||
|
@ -86,13 +105,15 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final innerPadding = EdgeInsets.all(8.0);
|
final innerPadding = EdgeInsets.all(8.0);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
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(
|
return BlurredRect(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: kOverlayBackground,
|
color: kOverlayBackground,
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: mediaQuery.viewInsets + mediaQuery.viewPadding.copyWith(top: 0),
|
padding: viewInsets + viewPadding.copyWith(top: 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: innerPadding,
|
padding: innerPadding,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
|
@ -187,22 +208,26 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class OverlayButton extends StatelessWidget {
|
class OverlayButton extends StatelessWidget {
|
||||||
|
final Animation<double> scale;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const OverlayButton({this.child});
|
const OverlayButton({Key key, this.scale, this.child}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlurredOval(
|
return ScaleTransition(
|
||||||
child: Material(
|
scale: scale,
|
||||||
type: MaterialType.circle,
|
child: BlurredOval(
|
||||||
color: kOverlayBackground,
|
child: Material(
|
||||||
child: Ink(
|
type: MaterialType.circle,
|
||||||
decoration: BoxDecoration(
|
color: kOverlayBackground,
|
||||||
border: Border.all(color: Colors.white30, width: 0.5),
|
child: Ink(
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.white30, width: 0.5),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
child: child,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue