Created
August 5, 2019 17:38
-
-
Save nathanosoares/e3458b24cc4cd294d201ae959ef6105f 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 'dart:math'; | |
import 'package:flutter/material.dart'; | |
import 'package:myapp/components/footer_item_widget.dart'; | |
import 'package:myapp/components/top_widget.dart'; | |
class RootPage extends StatefulWidget { | |
@override | |
RootPageState createState() => RootPageState(); | |
} | |
class RootPageState extends State<RootPage> | |
with SingleTickerProviderStateMixin { | |
PageController _controller; | |
double _top = 0; | |
bool _goingUp = false; | |
GlobalKey _emptyExpandedKey = GlobalKey(); | |
Size _emptyExpandedSize = Size(0, 0); | |
Offset _emptyExpandedPosition = Offset(0, 0); | |
AnimationController _animationController; | |
double _maxAnimationTop; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = PageController(); | |
_animationController = | |
AnimationController(vsync: this, duration: Duration(milliseconds: 100)); | |
WidgetsBinding.instance.addPostFrameCallback((_) { | |
_getEmptyExpandedSize(); | |
_getEmptyExpandedPosition(); | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
EdgeInsets padding = MediaQuery.of(context).padding; | |
double _height = | |
MediaQuery.of(context).size.height - padding.top - padding.bottom; | |
double _width = | |
MediaQuery.of(context).size.width - padding.left - padding.right; | |
Widget _pageView = PageView.builder( | |
controller: _controller, | |
itemCount: 3, | |
scrollDirection: Axis.horizontal, | |
itemBuilder: (context, index) => Padding( | |
padding: EdgeInsets.only( | |
left: _height * 0.035, right: _height * 0.035), | |
child: Container( | |
decoration: BoxDecoration( | |
color: Colors.white, | |
borderRadius: BorderRadius.all(Radius.circular(2))), | |
), | |
)); | |
Widget emptyExpanded = Expanded( | |
key: _emptyExpandedKey, | |
child: Container(), | |
); | |
setState(() { | |
this._maxAnimationTop = _height - | |
_emptyExpandedPosition.dy - | |
(_emptyExpandedSize.height * 0.3); | |
}); | |
double currentTopPercentage = 100 / _maxAnimationTop * _top; | |
double footerOpacity = 1.0 - max(0, min(1, currentTopPercentage / 100)); | |
return Scaffold( | |
backgroundColor: Color.fromRGBO(130, 38, 158, 1), | |
body: SafeArea( | |
child: Padding( | |
padding: EdgeInsets.only(top: _height * 0.035, bottom: _height * 0.035), | |
child: Column( | |
children: <Widget>[ | |
TopWidget(), | |
Expanded( | |
child: Stack( | |
fit: StackFit.expand, | |
overflow: Overflow.visible, | |
children: <Widget>[ | |
Column(children: <Widget>[ | |
emptyExpanded, | |
Opacity( | |
opacity: footerOpacity, | |
child: Padding( | |
padding: EdgeInsets.only( | |
top: _height * 0.04, bottom: _height * 0.04), | |
child: PageIndicator( | |
controller: _controller, | |
), | |
), | |
), | |
Opacity( | |
opacity: footerOpacity, | |
child: Container( | |
height: 95.0, | |
child: ListView.separated( | |
scrollDirection: Axis.horizontal, | |
padding: EdgeInsets.only( | |
left: _height * 0.027, right: _height * 0.027), | |
itemCount: footerItems.length, | |
itemBuilder: (BuildContext context, int index) => | |
Padding( | |
padding: EdgeInsets.only( | |
left: _height * 0.008, | |
right: _height * 0.008), | |
child: Container( | |
width: 95, | |
decoration: BoxDecoration( | |
color: Color.fromRGBO(145, 64, 169, 1), | |
borderRadius: | |
BorderRadius.all(Radius.circular(4))), | |
child: Padding( | |
padding: EdgeInsets.all(8), | |
child: footerItems[index], | |
), | |
), | |
), | |
separatorBuilder: (context, index) => Divider( | |
color: Colors.transparent, | |
), | |
)), | |
), | |
]), | |
Container( | |
constraints: BoxConstraints( | |
maxWidth: _emptyExpandedSize.width, | |
maxHeight: _emptyExpandedSize.height), | |
child: Padding( | |
padding: EdgeInsets.only(top: _height * 0.035), | |
child: Stack( | |
overflow: Overflow.visible, | |
fit: StackFit.expand, | |
children: <Widget>[ | |
Positioned( | |
top: _top, | |
width: _emptyExpandedSize.width, | |
height: _emptyExpandedSize.height - _height * 0.04, | |
child: GestureDetector( | |
behavior: HitTestBehavior.translucent, | |
child: NotificationListener( | |
child: _pageView, | |
), | |
onTap: () { | |
if (_top == _maxAnimationTop) { | |
_animate(AnimateDirection.UP); | |
} | |
}, | |
onPanUpdate: (DragUpdateDetails details) { | |
if (_animationController.isAnimating) { | |
_animationController.stop(canceled: true); | |
} | |
if (_top + details.delta.dy > 0 && | |
_top <= _maxAnimationTop) { | |
bool goingUp = false; | |
if (details.delta.dy < 0) { | |
goingUp = true; | |
} | |
setState(() { | |
_top += details.delta.dy; | |
_goingUp = goingUp; | |
}); | |
} | |
}, | |
onPanEnd: (DragEndDetails details) { | |
if (_goingUp || currentTopPercentage <= 20) { | |
_animate(AnimateDirection.UP); | |
} else { | |
_animate(AnimateDirection.DOWN); | |
} | |
}, | |
), | |
), | |
], | |
), | |
), | |
), | |
], | |
), | |
) | |
], | |
), | |
)), | |
); | |
} | |
_animate(AnimateDirection direction) { | |
_animationController.reset(); | |
Animation animation; | |
if (direction == AnimateDirection.UP) { | |
animation = | |
Tween<double>(begin: _top, end: 0).animate(_animationController); | |
setState(() { | |
this._goingUp = false; | |
}); | |
} else { | |
animation = Tween<double>(begin: _top, end: _maxAnimationTop) | |
.animate(_animationController); | |
} | |
Function listener = () { | |
setState(() { | |
_top = animation.value; | |
}); | |
}; | |
_animationController.addListener(listener); | |
_animationController.addStatusListener((AnimationStatus status) { | |
if (status == AnimationStatus.completed) { | |
_animationController.removeListener(listener); | |
} | |
}); | |
_animationController.forward(); | |
} | |
_getEmptyExpandedSize() { | |
final RenderBox containerRenderBox = | |
_emptyExpandedKey.currentContext.findRenderObject(); | |
final containerSize = containerRenderBox.size; | |
setState(() { | |
_emptyExpandedSize = containerSize; | |
}); | |
} | |
_getEmptyExpandedPosition() { | |
final RenderBox containerRenderBox = | |
_emptyExpandedKey.currentContext.findRenderObject(); | |
final containerPosition = containerRenderBox.localToGlobal(Offset.zero); | |
setState(() { | |
_emptyExpandedPosition = containerPosition; | |
}); | |
} | |
} | |
enum AnimateDirection { UP, DOWN } | |
class PageIndicator extends StatefulWidget { | |
final PageController controller; | |
PageIndicator({ | |
@required this.controller, | |
}); | |
@override | |
State<StatefulWidget> createState() => PageIndicatorState(); | |
} | |
class PageIndicatorState extends State<PageIndicator> { | |
PageController get controller => widget.controller; | |
@override | |
void initState() { | |
super.initState(); | |
controller.addListener(scrollListener); | |
} | |
@override | |
void dispose() { | |
controller.removeListener(scrollListener); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if (!controller.hasClients) { | |
return Container(); | |
} | |
CustomPainter indicatorPainter = CirclePainter( | |
color: Color.fromRGBO(255, 255, 255, .3), | |
selectedColor: Colors.white, | |
count: 3, | |
page: widget.controller.page ?? controller.initialPage.toDouble(), | |
padding: 4, | |
radius: 4 / 2, | |
); | |
return CustomPaint( | |
painter: indicatorPainter, | |
); | |
} | |
void scrollListener() { | |
setState(() {}); | |
} | |
} | |
class CirclePainter extends CustomPainter { | |
double page; | |
int count; | |
Color color; | |
Color selectedColor; | |
double radius; | |
double padding; | |
Paint _circlePaint; | |
Paint _selectedPaint; | |
CirclePainter({ | |
this.page = 0.0, | |
this.count = 0, | |
this.color = Colors.white, | |
this.selectedColor = Colors.grey, | |
this.radius = 12.0, | |
this.padding = 5.0, | |
}) { | |
_circlePaint = Paint(); | |
_circlePaint.color = color; | |
_selectedPaint = Paint(); | |
_selectedPaint.color = selectedColor; | |
this.page ??= 0.0; | |
this.count ??= 0; | |
this.color ??= Colors.white; | |
this.selectedColor ??= Colors.grey; | |
this.radius ??= 12.0; | |
this.padding ??= 5.0; | |
} | |
double get totalWidth => count * radius * 2 + padding * (count - 1); | |
@override | |
void paint(Canvas canvas, Size size) { | |
var centerWidth = size.width / 2; | |
var startX = centerWidth - totalWidth / 2; | |
for (var i = 0; i < count ?? 0; i++) { | |
var x = startX + i * (radius * 2 + padding) + radius; | |
canvas.drawCircle(Offset(x, radius), radius, _circlePaint); | |
} | |
var selectedX = startX + page * (radius * 2 + padding) + radius; | |
canvas.drawCircle(Offset(selectedX, radius), radius, _selectedPaint); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) => true; | |
} |
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'; | |
class TopWidget extends StatelessWidget { | |
const TopWidget({ | |
Key key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
children: <Widget>[ | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Image.network( | |
"https://aws1.discourse-cdn.com/nubank/original/1X/0e91acb8692ef95f8446675084ced03e892f16c2.png", | |
height: MediaQuery.of(context).size.width * 0.085, | |
color: Colors.white, | |
fit: BoxFit.fitWidth, | |
filterQuality: FilterQuality.high, | |
), | |
Padding( | |
padding: EdgeInsets.fromLTRB(8, 0, 0, 0), | |
child: Text( | |
"Nathan", | |
style: TextStyle( | |
color: Colors.white, | |
fontSize: MediaQuery.of(context).size.width * 0.065, | |
fontWeight: FontWeight.bold), | |
), | |
) | |
], | |
), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Opacity( | |
opacity: 0.5, | |
child: Icon( | |
Icons.keyboard_arrow_down, | |
color: Colors.white, | |
size: 25, | |
), | |
), | |
], | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment