1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00
Files
samples/gallery/lib/pages/backdrop.dart
2020-02-05 14:11:54 -05:00

379 lines
12 KiB
Dart

// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flare_dart/math/mat2d.dart';
import 'package:flare_flutter/flare.dart';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flare_flutter/flare_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:gallery/constants.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/pages/home.dart';
import 'package:gallery/pages/settings.dart';
class AnimatedBackdrop extends StatefulWidget {
@override
_AnimatedBackdropState createState() => _AnimatedBackdropState();
}
class _AnimatedBackdropState extends State<AnimatedBackdrop>
with SingleTickerProviderStateMixin {
AnimationController backdropController;
ValueNotifier<bool> isSettingsOpenNotifier;
Animation<double> openSettingsAnimation;
Animation<double> staggerSettingsItemsAnimation;
@override
void initState() {
super.initState();
backdropController = AnimationController(
duration: Duration(milliseconds: 200),
vsync: this,
)..addListener(() {
setState(() {
// The state that has changed here is the animation.
});
});
isSettingsOpenNotifier = ValueNotifier(false);
openSettingsAnimation = CurvedAnimation(
parent: backdropController,
curve: Interval(
0.0,
0.4,
curve: Curves.ease,
),
);
staggerSettingsItemsAnimation = CurvedAnimation(
parent: backdropController,
curve: Interval(
0.5,
1.0,
curve: Curves.easeIn,
),
);
}
@override
void dispose() {
backdropController.dispose();
isSettingsOpenNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Backdrop(
controller: backdropController,
isSettingsOpenNotifier: isSettingsOpenNotifier,
openSettingsAnimation: openSettingsAnimation,
frontLayer: SettingsPage(
openSettingsAnimation: openSettingsAnimation,
staggerSettingsItemsAnimation: staggerSettingsItemsAnimation,
isSettingsOpenNotifier: isSettingsOpenNotifier,
),
backLayer: HomePage(),
);
}
}
class Backdrop extends StatefulWidget {
final Widget frontLayer;
final Widget backLayer;
final AnimationController controller;
final Animation<double> openSettingsAnimation;
final ValueNotifier<bool> isSettingsOpenNotifier;
Backdrop({
Key key,
@required this.frontLayer,
@required this.backLayer,
@required this.controller,
@required this.openSettingsAnimation,
@required this.isSettingsOpenNotifier,
}) : assert(frontLayer != null),
assert(backLayer != null),
assert(controller != null),
assert(isSettingsOpenNotifier != null),
assert(openSettingsAnimation != null),
super(key: key);
@override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin, FlareController {
FlareAnimationLayer _animationLayer;
FlutterActorArtboard _artboard;
double settingsButtonWidth = 64;
double settingsButtonHeightDesktop = 56;
double settingsButtonHeightMobile = 40;
FocusNode frontLayerFocusNode;
FocusNode backLayerFocusNode;
@override
void initState() {
super.initState();
frontLayerFocusNode = FocusNode();
backLayerFocusNode = FocusNode();
}
@override
void dispose() {
frontLayerFocusNode.dispose();
backLayerFocusNode.dispose();
super.dispose();
}
@override
void initialize(FlutterActorArtboard artboard) {
_artboard = artboard;
initAnimationLayer();
}
@override
void setViewTransform(Mat2D viewTransform) {
// This is a necessary override for the [FlareController] mixin.
}
@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
if (_animationLayer != null) {
FlareAnimationLayer layer = _animationLayer;
layer.time = widget.controller.value * layer.duration;
layer.animation.apply(layer.time, _artboard, 1);
if (layer.isDone || layer.time == 0) {
_animationLayer = null;
}
}
return _animationLayer != null;
}
void initAnimationLayer() {
if (_artboard != null) {
final animationName = "Animations";
ActorAnimation animation = _artboard.getAnimation(animationName);
_animationLayer = FlareAnimationLayer()
..name = animationName
..animation = animation;
}
}
void toggleSettings() {
// Animate the settings panel to open or close.
widget.controller
.fling(velocity: widget.isSettingsOpenNotifier.value ? -1 : 1);
setState(() {
widget.isSettingsOpenNotifier.value =
!widget.isSettingsOpenNotifier.value;
});
// Animate the settings icon.
initAnimationLayer();
isActive.value = true;
}
Animation<RelativeRect> _getPanelAnimation(BoxConstraints constraints) {
final double height = constraints.biggest.height;
final double top = height - galleryHeaderHeight;
final double bottom = -galleryHeaderHeight;
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0, 0, 0, 0),
end: RelativeRect.fromLTRB(0, top, 0, bottom),
).animate(CurvedAnimation(
parent: widget.openSettingsAnimation,
curve: Curves.linear,
));
}
Widget _galleryHeader() {
return ExcludeSemantics(
excluding: widget.isSettingsOpenNotifier.value,
child: Semantics(
sortKey: OrdinalSortKey(
GalleryOptions.of(context).textDirection() == TextDirection.ltr
? 1.0
: 2.0,
name: 'header',
),
label: GalleryLocalizations.of(context).homeHeaderGallery,
child: Container(),
),
);
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final isDesktop = isDisplayDesktop(context);
final safeAreaTopPadding = MediaQuery.of(context).padding.top;
final Widget frontLayer = ExcludeSemantics(
child: DefaultFocusTraversal(
policy: WidgetOrderFocusTraversalPolicy(),
child: Focus(
skipTraversal: !widget.isSettingsOpenNotifier.value,
child: widget.frontLayer,
),
),
excluding: !widget.isSettingsOpenNotifier.value,
);
final Widget backLayer = ExcludeSemantics(
child: widget.backLayer,
excluding: widget.isSettingsOpenNotifier.value,
);
return DefaultFocusTraversal(
child: InheritedBackdropFocusNodes(
frontLayerFocusNode: frontLayerFocusNode,
backLayerFocusNode: backLayerFocusNode,
child: Container(
child: Stack(
children: [
if (!isDesktop) ...[
_galleryHeader(),
frontLayer,
PositionedTransition(
rect: _getPanelAnimation(constraints),
child: backLayer,
),
],
if (isDesktop) ...[
_galleryHeader(),
backLayer,
if (widget.isSettingsOpenNotifier.value) ...[
ExcludeSemantics(
child: ModalBarrier(
dismissible: true,
),
),
Semantics(
label: GalleryLocalizations.of(context)
.settingsButtonCloseLabel,
child: GestureDetector(
onTap: toggleSettings,
),
)
],
ScaleTransition(
alignment: Directionality.of(context) == TextDirection.ltr
? Alignment.topRight
: Alignment.topLeft,
scale: CurvedAnimation(
parent: isDesktop
? widget.controller
: widget.openSettingsAnimation,
curve: Curves.easeIn,
reverseCurve: Curves.easeOut,
),
child: Align(
alignment: AlignmentDirectional.topEnd,
child: Material(
elevation: 7,
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(40),
color: Theme.of(context).colorScheme.secondaryVariant,
child: Container(
constraints: const BoxConstraints(
maxHeight: 560,
maxWidth: desktopSettingsWidth,
minWidth: desktopSettingsWidth,
),
child: frontLayer,
),
),
),
),
],
Align(
alignment: AlignmentDirectional.topEnd,
child: Semantics(
button: true,
label: widget.isSettingsOpenNotifier.value
? GalleryLocalizations.of(context)
.settingsButtonCloseLabel
: GalleryLocalizations.of(context).settingsButtonLabel,
child: SizedBox(
width: settingsButtonWidth,
height: isDesktop
? settingsButtonHeightDesktop
: settingsButtonHeightMobile + safeAreaTopPadding,
child: Material(
borderRadius: BorderRadiusDirectional.only(
bottomStart: Radius.circular(10),
),
color: widget.isSettingsOpenNotifier.value &
!widget.controller.isAnimating
? Colors.transparent
: Theme.of(context).colorScheme.secondaryVariant,
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: toggleSettings,
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 3, end: 18),
child: Focus(
onFocusChange: (hasFocus) {
if (!hasFocus) {
FocusScope.of(context).requestFocus(
(widget.isSettingsOpenNotifier.value)
? frontLayerFocusNode
: backLayerFocusNode);
}
},
child: FlareActor(
Theme.of(context).colorScheme.brightness ==
Brightness.light
? 'assets/icons/settings/settings_light.flr'
: 'assets/icons/settings/settings_dark.flr',
alignment: Directionality.of(context) ==
TextDirection.ltr
? Alignment.bottomLeft
: Alignment.bottomRight,
fit: BoxFit.contain,
controller: this,
),
),
),
),
),
),
),
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: _buildStack,
);
}
}
class InheritedBackdropFocusNodes extends InheritedWidget {
InheritedBackdropFocusNodes({
@required Widget child,
@required this.frontLayerFocusNode,
@required this.backLayerFocusNode,
}) : assert(child != null),
super(child: child);
final FocusNode frontLayerFocusNode;
final FocusNode backLayerFocusNode;
static InheritedBackdropFocusNodes of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType();
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
}