// 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:flutter/material.dart'; /// Animations class to compute animation values for overlay widgets. /// /// Values are loosely based on Material Design specs, which are minimal. class Animations { final AnimationController openController; final AnimationController tapController; final AnimationController rippleController; final AnimationController dismissController; static const backgroundMaxOpacity = 0.96; static const backgroundTapRadius = 20.0; static const rippleMaxOpacity = 0.75; static const tapTargetToContentDistance = 20.0; static const tapTargetMaxRadius = 44.0; static const tapTargetMinRadius = 20.0; static const tapTargetRippleRadius = 64.0; Animations( this.openController, this.tapController, this.rippleController, this.dismissController, ); Animation backgroundOpacity(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(0); case FeatureDiscoveryStatus.open: return Tween(begin: 0, end: backgroundMaxOpacity) .animate(CurvedAnimation( parent: openController, curve: Interval(0, 0.5, curve: Curves.ease), )); case FeatureDiscoveryStatus.tap: return Tween(begin: backgroundMaxOpacity, end: 0) .animate(CurvedAnimation( parent: tapController, curve: Curves.ease, )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: backgroundMaxOpacity, end: 0) .animate(CurvedAnimation( parent: dismissController, curve: Interval(0.2, 1.0, curve: Curves.ease), )); default: return AlwaysStoppedAnimation(backgroundMaxOpacity); } } Animation backgroundRadius( FeatureDiscoveryStatus status, double backgroundRadiusMax, ) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(0); case FeatureDiscoveryStatus.open: return Tween(begin: 0, end: backgroundRadiusMax) .animate(CurvedAnimation( parent: openController, curve: Interval(0, 0.5, curve: Curves.ease), )); case FeatureDiscoveryStatus.tap: return Tween( begin: backgroundRadiusMax, end: backgroundRadiusMax + backgroundTapRadius) .animate(CurvedAnimation( parent: tapController, curve: Curves.ease, )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: backgroundRadiusMax, end: 0) .animate(CurvedAnimation( parent: dismissController, curve: Curves.ease, )); default: return AlwaysStoppedAnimation(backgroundRadiusMax); } } Animation backgroundCenter( FeatureDiscoveryStatus status, Offset start, Offset end, ) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(start); case FeatureDiscoveryStatus.open: return Tween(begin: start, end: end).animate(CurvedAnimation( parent: openController, curve: Interval(0, 0.5, curve: Curves.ease), )); case FeatureDiscoveryStatus.tap: return Tween(begin: end, end: start).animate(CurvedAnimation( parent: tapController, curve: Curves.ease, )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: end, end: start).animate(CurvedAnimation( parent: dismissController, curve: Curves.ease, )); default: return AlwaysStoppedAnimation(end); } } Animation contentOpacity(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(0); case FeatureDiscoveryStatus.open: return Tween(begin: 0, end: 1.0).animate(CurvedAnimation( parent: openController, curve: Interval(0.4, 0.7, curve: Curves.ease), )); case FeatureDiscoveryStatus.tap: return Tween(begin: 1.0, end: 0).animate(CurvedAnimation( parent: tapController, curve: Interval(0, 0.4, curve: Curves.ease), )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: 1.0, end: 0).animate(CurvedAnimation( parent: dismissController, curve: Interval(0, 0.4, curve: Curves.ease), )); default: return AlwaysStoppedAnimation(1.0); } } Animation rippleOpacity(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.ripple: return Tween(begin: rippleMaxOpacity, end: 0) .animate(CurvedAnimation( parent: rippleController, curve: Interval(0.3, 0.8, curve: Curves.ease), )); default: return AlwaysStoppedAnimation(0); } } Animation rippleRadius(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.ripple: if (rippleController.value >= 0.3 && rippleController.value <= 0.8) { return Tween(begin: tapTargetMaxRadius, end: 79.0) .animate(CurvedAnimation( parent: rippleController, curve: Interval(0.3, 0.8, curve: Curves.ease), )); } return AlwaysStoppedAnimation(tapTargetMaxRadius); default: return AlwaysStoppedAnimation(0); } } Animation tapTargetOpacity(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(0); case FeatureDiscoveryStatus.open: return Tween(begin: 0, end: 1.0).animate(CurvedAnimation( parent: openController, curve: Interval(0, 0.4, curve: Curves.ease), )); case FeatureDiscoveryStatus.tap: return Tween(begin: 1.0, end: 0).animate(CurvedAnimation( parent: tapController, curve: Interval(0.1, 0.6, curve: Curves.ease), )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: 1.0, end: 0).animate(CurvedAnimation( parent: dismissController, curve: Interval(0.2, 0.8, curve: Curves.ease), )); default: return AlwaysStoppedAnimation(1.0); } } Animation tapTargetRadius(FeatureDiscoveryStatus status) { switch (status) { case FeatureDiscoveryStatus.closed: return AlwaysStoppedAnimation(tapTargetMinRadius); case FeatureDiscoveryStatus.open: return Tween(begin: tapTargetMinRadius, end: tapTargetMaxRadius) .animate(CurvedAnimation( parent: openController, curve: Interval(0, 0.4, curve: Curves.ease), )); case FeatureDiscoveryStatus.ripple: if (rippleController.value < 0.3) { return Tween( begin: tapTargetMaxRadius, end: tapTargetRippleRadius) .animate(CurvedAnimation( parent: rippleController, curve: Interval(0, 0.3, curve: Curves.ease), )); } else if (rippleController.value < 0.6) { return Tween( begin: tapTargetRippleRadius, end: tapTargetMaxRadius) .animate(CurvedAnimation( parent: rippleController, curve: Interval(0.3, 0.6, curve: Curves.ease), )); } return AlwaysStoppedAnimation(tapTargetMaxRadius); case FeatureDiscoveryStatus.tap: return Tween(begin: tapTargetMaxRadius, end: tapTargetMinRadius) .animate(CurvedAnimation( parent: tapController, curve: Curves.ease, )); case FeatureDiscoveryStatus.dismiss: return Tween(begin: tapTargetMaxRadius, end: tapTargetMinRadius) .animate(CurvedAnimation( parent: dismissController, curve: Curves.ease, )); default: return AlwaysStoppedAnimation(tapTargetMaxRadius); } } } /// Enum to indicate the current status of a [FeatureDiscovery] widget. enum FeatureDiscoveryStatus { closed, // Overlay is closed. open, // Overlay is opening. ripple, // Overlay is rippling. tap, // Overlay is tapped. dismiss, // Overlay is being dismissed. }