Last active
January 8, 2022 17:27
-
-
Save mkiisoft/0b6247558705c2726f2ce5fb79deecec to your computer and use it in GitHub Desktop.
Flutter - Yeti by Mariano Zorrilla
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'; | |
void main() { | |
runApp(YetiApp()); | |
} | |
enum ArmAnim { | |
NORMAL, | |
HI, | |
HIDE, | |
} | |
class YetiApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Yeti', | |
home: YetiScreen(), | |
); | |
} | |
} | |
class YetiScreen extends StatefulWidget { | |
@override | |
_YetiScreenState createState() => _YetiScreenState(); | |
} | |
class _YetiScreenState extends State<YetiScreen> with TickerProviderStateMixin { | |
static String baseUrl = 'https://firebasestorage.googleapis.com/'; | |
static String baseRepo = 'v0/b/flutter-yeti.appspot.com/o/'; | |
Offset hoverPos = Offset.zero; | |
String eyesPath = baseUrl + baseRepo + 'eye.png?alt=media'; | |
double mouthLocation = -0.05; | |
ArmAnim _leftArmAnim = ArmAnim.NORMAL; | |
ArmAnim _rightArmAnim = ArmAnim.NORMAL; | |
double animateHandHi = 0.15; | |
AnimationController _hiController; | |
Animation<double> _hiAnimation; | |
double animateHandHide = 0.25; | |
AnimationController _hideController; | |
Animation<double> _hideAnimation; | |
bool _armsOpen = true; | |
MaterialColor white = MaterialColor( | |
0xFFFFFFFF, | |
<int, Color>{ | |
50: Color(0xFFFFFFFF), | |
100: Color(0xFFFFFFFF), | |
200: Color(0xFFFFFFFF), | |
300: Color(0xFFFFFFFF), | |
400: Color(0xFFFFFFFF), | |
500: Color(0xFFFFFFFF), | |
600: Color(0xFFFFFFFF), | |
700: Color(0xFFFFFFFF), | |
800: Color(0xFFFFFFFF), | |
900: Color(0xFFFFFFFF), | |
}, | |
); | |
Widget powered() { | |
return Align( | |
alignment: Alignment.bottomCenter, | |
child: Container( | |
width: 150, | |
child: Padding( | |
padding: EdgeInsets.only(bottom: 20, top: 10), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Text( | |
'Powered by', | |
style: TextStyle(fontFamily: 'Sans', fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600), | |
), | |
SizedBox(width: 5), | |
Semantics( | |
child: FlutterLogo(colors: white), | |
label: "Flutter", | |
readOnly: true, | |
) | |
], | |
), | |
), | |
), | |
); | |
} | |
void _changeImage() { | |
setState(() { | |
eyesPath = baseUrl + baseRepo + 'eye_happy.png?alt=media'; | |
mouthLocation = -0.08; | |
_armsOpen = false; | |
}); | |
Future.delayed(Duration(milliseconds: 1000), () { | |
setState(() { | |
eyesPath = baseUrl + baseRepo + 'eye.png?alt=media'; | |
mouthLocation = -0.05; | |
_armsOpen = true; | |
}); | |
}); | |
} | |
void _animateHi() { | |
setState(() => _leftArmAnim = ArmAnim.HI); | |
_hiAnimation = Tween<double>(begin: 0.15, end: 0.45).animate(_hiController); | |
_hiController.forward(); | |
_hiAnimation.addListener(() { | |
setState(() => animateHandHi = _hiAnimation.value); | |
}); | |
Stream.periodic(Duration(milliseconds: 500), (value) => value).take(5).listen((event) { | |
event % 2 == 0 ? _hiController.reverse() : _hiController.forward(); | |
}).onDone(() => setState(() => _leftArmAnim = ArmAnim.NORMAL)); | |
} | |
void _animateHide() { | |
setState(() { | |
_leftArmAnim = ArmAnim.HIDE; | |
_rightArmAnim = ArmAnim.HIDE; | |
}); | |
_hideAnimation = Tween<double>(begin: 0.25, end: -0.15).animate(_hideController); | |
_hideAnimation.addListener(() { | |
setState(() => animateHandHide = _hideAnimation.value); | |
}); | |
Future.delayed(Duration(milliseconds: 1000), () { | |
_hideController.forward(); | |
Future.delayed(Duration(milliseconds: 1500), () { | |
setState(() { | |
_leftArmAnim = ArmAnim.NORMAL; | |
_rightArmAnim = ArmAnim.NORMAL; | |
}); | |
_hideController.value = 0.0; | |
}); | |
}); | |
} | |
Widget hiAnimation() { | |
return Align( | |
alignment: Alignment(-0.5, 0.7), | |
child: Transform( | |
transform: Matrix4.identity()..setRotationX(135), | |
child: Transform( | |
transform: Matrix4.rotationZ(animateHandHi), | |
alignment: Alignment.topLeft, | |
child: Image.network(baseUrl + baseRepo + 'left_arm.png?alt=media', width: 180), | |
), | |
), | |
); | |
} | |
Widget hideLeftAnimation() { | |
return Align( | |
alignment: Alignment(-0.5, 0.7), | |
child: Transform( | |
transform: Matrix4.identity()..setRotationX(135), | |
child: Transform( | |
transform: Matrix4.rotationZ(-0.25), | |
alignment: Alignment.topLeft, | |
child: Image.network(baseUrl + baseRepo + 'left_arm.png?alt=media', width: 180), | |
), | |
), | |
); | |
} | |
Widget hideRightAnimation() { | |
return Align( | |
alignment: Alignment(0.5, 0.7), | |
child: Transform( | |
transform: Matrix4.identity()..setRotationX(135), | |
child: Transform( | |
transform: Matrix4.rotationZ(animateHandHide), | |
alignment: Alignment.topRight, | |
child: Image.network(baseUrl + baseRepo + 'right_arm.png?alt=media', width: 180), | |
), | |
), | |
); | |
} | |
Widget hideAnimation(bool leftArm) { | |
return when(leftArm, { | |
true: hideLeftAnimation(), | |
false: hideRightAnimation(), | |
}); | |
} | |
Widget leftArm() { | |
return GestureDetector( | |
onTap: () => _animateHi(), | |
child: Align( | |
alignment: Alignment(-0.5, 0.5), | |
child: TweenAnimationBuilder<Matrix4>( | |
duration: Duration(milliseconds: 500), | |
tween: _armsOpen | |
? Matrix4Tween(begin: Matrix4.rotationZ(-0.3), end: Matrix4.rotationZ(0.0)) | |
: Matrix4Tween(begin: Matrix4.rotationZ(0.0), end: Matrix4.rotationZ(-0.3)), | |
builder: (BuildContext context, Matrix4 value, Widget child) { | |
return Transform( | |
transform: value, | |
child: child, | |
alignment: Alignment.topRight, | |
); | |
}, | |
child: Image.network(baseUrl + baseRepo + 'left_arm.png?alt=media', width: 180), | |
), | |
), | |
); | |
} | |
Widget rightArm() { | |
return Align( | |
alignment: Alignment(0.5, 0.5), | |
child: TweenAnimationBuilder<Matrix4>( | |
duration: Duration(milliseconds: 500), | |
tween: _armsOpen | |
? Matrix4Tween(begin: Matrix4.rotationZ(0.3), end: Matrix4.rotationZ(0.0)) | |
: Matrix4Tween(begin: Matrix4.rotationZ(0.0), end: Matrix4.rotationZ(0.3)), | |
builder: (BuildContext context, Matrix4 value, Widget child) { | |
return Transform( | |
transform: value, | |
child: child, | |
); | |
}, | |
child: Image.network(baseUrl + baseRepo + 'right_arm.png?alt=media', width: 180), | |
), | |
); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
_hiController = AnimationController(vsync: this, duration: Duration(milliseconds: 500)); | |
_hideController = AnimationController(vsync: this, duration: Duration(milliseconds: 500)); | |
} | |
@override | |
void dispose() { | |
_hiController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Material( | |
child: Stack( | |
fit: StackFit.expand, | |
children: [ | |
Image.network(baseUrl + baseRepo + 'forest.jpg?alt=media', fit: BoxFit.cover), | |
Opacity( | |
opacity: 0.6, | |
child: Container( | |
decoration: BoxDecoration( | |
gradient: LinearGradient( | |
colors: [Color(0xFF512DA8), Color(0xFFF57C00)], | |
begin: Alignment.topCenter, | |
end: Alignment.bottomCenter, | |
), | |
), | |
), | |
), | |
powered(), | |
MouseRegion( | |
onHover: (details) { | |
setState(() { | |
final size = context.size; | |
final center = size.center(Offset.zero); | |
hoverPos = Offset( | |
(details.position.dx - center.dx) / size.width, | |
(details.position.dy - center.dy) / size.height, | |
); | |
}); | |
}, | |
child: GestureDetector( | |
onPanUpdate: (details) { | |
setState(() { | |
final size = context.size; | |
final center = size.center(Offset.zero); | |
hoverPos = Offset( | |
(details.localPosition.dx - center.dx) / size.width, | |
(details.localPosition.dy - center.dy) / size.height, | |
); | |
}); | |
}, | |
onPanEnd: (_) => setState(() => hoverPos = Offset.zero), | |
onPanCancel: () => setState(() => hoverPos = Offset.zero), | |
child: Transform.scale( | |
scale: 0.8, | |
child: FittedBox( | |
child: SizedBox( | |
height: 800, | |
width: 800, | |
child: GestureDetector( | |
behavior: HitTestBehavior.opaque, | |
onTap: () => _changeImage(), | |
child: Stack( | |
fit: StackFit.expand, | |
children: [ | |
Image.network(baseUrl + baseRepo + 'body.png?alt=media'), | |
Align( | |
alignment: Alignment(hoverPos.dx * 0.1, hoverPos.dy * 0.1 - 0.22), | |
child: GestureDetector( | |
onTap: () => _animateHide(), | |
child: Row( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Image.network(eyesPath, width: 40), | |
SizedBox(width: 120), | |
Image.network(eyesPath, width: 40), | |
], | |
), | |
), | |
), | |
AnimatedAlign( | |
duration: Duration(milliseconds: 500), | |
alignment: Alignment(0.0, mouthLocation), | |
child: Image.network(baseUrl + baseRepo + 'mouth.png?alt=media', width: 60), | |
), | |
when(_leftArmAnim, { | |
ArmAnim.NORMAL: leftArm(), | |
ArmAnim.HI: hiAnimation(), | |
ArmAnim.HIDE: hideAnimation(true), | |
}), | |
when(_rightArmAnim, { | |
ArmAnim.NORMAL: rightArm(), | |
ArmAnim.HIDE: hideAnimation(false), | |
}), | |
], | |
), | |
), | |
), | |
), | |
), | |
), | |
), | |
], | |
), | |
); | |
} | |
} | |
Type when<Input, Type>(Input selectedOption, Map<Input, Type> branches, [Type defaultValue]) { | |
if (!branches.containsKey(selectedOption)) { | |
return defaultValue; | |
} | |
return branches[selectedOption]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment