Last active
September 27, 2023 16:55
-
-
Save HansMuller/e79d5d26428f27a726d49ddc0e09bc00 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
/// Animated text widget that will animate changes in [temperature] to match | |
/// the effect on the new thermostats. | |
class AnimatedTemperatureText extends StatefulWidget { | |
final int temperature; | |
final Color textColor; | |
const AnimatedTemperatureText({ | |
super.key, | |
required this.temperature, | |
required this.textColor, | |
}); | |
@override | |
State<AnimatedTemperatureText> createState() => | |
_AnimatedTemperatureTextState(); | |
} | |
enum _SlideAnimationDirection { | |
up, | |
down, | |
} | |
class _AnimatedTemperatureTextState extends State<AnimatedTemperatureText> { | |
/// Which direction the digits should animate, set in [didUpdateWidget]. | |
_SlideAnimationDirection slideAnimationDirection = | |
_SlideAnimationDirection.up; | |
@override | |
void didUpdateWidget(AnimatedTemperatureText oldWidget) { | |
// Determine if we increased or decreased in temperature, and if so, | |
// animate. | |
if (widget.temperature != oldWidget.temperature) { | |
slideAnimationDirection = widget.temperature > oldWidget.temperature | |
? _SlideAnimationDirection.up | |
: _SlideAnimationDirection.down; | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
/// Returns the number in the first temperature digit, from 0-9. | |
/// | |
/// Example: Temperature is 53, this would return 5. | |
int firstTemperatureDigit(AnimatedTemperatureText widget) { | |
// The first digit is in the 10s place. | |
return widget.temperature ~/ 10; | |
} | |
/// Returns the number in the second temperature digit, from 0-9. | |
/// | |
/// Example: Temperature is 53, this would return 3. | |
int secondTemperatureDigit(AnimatedTemperatureText widget) { | |
// The second digit is in the 1s place. | |
return widget.temperature % 10; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return ClipRect( | |
child: Row( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
AnimatedDigit( | |
value: firstTemperatureDigit(widget), | |
textColor: widget.textColor, | |
animationDirection: slideAnimationDirection, | |
textAlign: TextAlign.start, | |
), | |
AnimatedDigit( | |
value: secondTemperatureDigit(widget), | |
textColor: widget.textColor, | |
animationDirection: slideAnimationDirection, | |
textAlign: TextAlign.end, | |
), | |
], | |
), | |
); | |
} | |
} | |
// Occupies the same width as the widest single digit used by AnimatedDigit. | |
class _PlaceholderDigit extends StatelessWidget { | |
const _PlaceholderDigit(); | |
@override | |
Widget build(BuildContext context) { | |
final TextStyle textStyle = Theme.of(context).textTheme.displayLarge!.copyWith( | |
fontWeight: FontWeight.w500, | |
); | |
final Iterable<Widget> placeholderDigits = <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map<Widget>( | |
(int n) { | |
return Opacity( | |
opacity: 0, | |
child: Text('$n', style: textStyle), | |
); | |
}, | |
); | |
return Stack(children: placeholderDigits.toList()); | |
} | |
} | |
class AnimatedDigit extends StatelessWidget { | |
/// Animation that goes from -1 to 0 on the y-axis, which looks like an | |
/// upward motion. | |
final Tween<Offset> upwardSlide = Tween<Offset>( | |
begin: const Offset(0, -1.0), | |
end: Offset.zero, | |
); | |
/// Animation that goes from 1 to 0 on the y-axis, which looks like a | |
/// downward motion. | |
final Tween<Offset> downwardSlide = Tween<Offset>( | |
begin: const Offset(0, 1.0), | |
end: Offset.zero, | |
); | |
final int value; | |
final Color textColor; | |
final _SlideAnimationDirection animationDirection; | |
final TextAlign textAlign; | |
AnimatedDigit({ | |
super.key, | |
required this.value, | |
required this.textColor, | |
required this.animationDirection, | |
required this.textAlign, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
final TextStyle textStyle = Theme.of(context).textTheme.displayLarge!.copyWith( | |
color: textColor, | |
fontWeight: FontWeight.w500, | |
); | |
return AnimatedSwitcher( | |
duration: const Duration(milliseconds: 1000), | |
switchInCurve: const Interval(0, 1, curve: Curves.easeInOut), | |
switchOutCurve: const Interval(0, 1, curve: Curves.easeInOut), | |
transitionBuilder: (child, animation) { | |
late Tween<Offset> outAnimation; | |
late Tween<Offset> inAnimation; | |
switch (animationDirection) { | |
case _SlideAnimationDirection.up: | |
outAnimation = upwardSlide; | |
inAnimation = downwardSlide; | |
break; | |
case _SlideAnimationDirection.down: | |
outAnimation = downwardSlide; | |
inAnimation = upwardSlide; | |
break; | |
} | |
if (child.key == ValueKey(value)) { | |
return SlideTransition( | |
position: inAnimation.animate(animation), | |
child: child, | |
); | |
} else { | |
return SlideTransition( | |
position: outAnimation.animate(animation), | |
child: child, | |
); | |
} | |
}, | |
child: Stack( | |
key: ValueKey(value), | |
children: <Widget>[ | |
const _PlaceholderDigit(), | |
Text(value.toString(), style: textStyle), | |
], | |
), | |
); | |
} | |
} | |
class AnimatedTemperatureHome extends StatefulWidget { | |
const AnimatedTemperatureHome({ super.key }); | |
@override | |
State<AnimatedTemperatureHome> createState() => _AnimatedTemperatureHomeState(); | |
} | |
class _AnimatedTemperatureHomeState extends State<AnimatedTemperatureHome> { | |
int temperature = 31; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: AnimatedTemperatureText( | |
temperature: temperature, | |
textColor: Colors.black, | |
), | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: () { | |
setState(() { temperature += 1; }); | |
}, | |
tooltip: 'Increment Digit', | |
child: const Icon(Icons.add), | |
), | |
); | |
} | |
} | |
class AnimatedTemperatureApp extends StatelessWidget { | |
const AnimatedTemperatureApp({ super.key }); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'AnimatedTemperature', | |
theme: ThemeData(useMaterial3: true), | |
home: const Scaffold( | |
body: Center( | |
child: AnimatedTemperatureHome(), | |
), | |
), | |
); | |
} | |
} | |
void main() { | |
runApp(const AnimatedTemperatureApp()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment