Skip to content

Instantly share code, notes, and snippets.

@nathanosoares
Created August 5, 2019 17:38
Show Gist options
  • Save nathanosoares/e3458b24cc4cd294d201ae959ef6105f to your computer and use it in GitHub Desktop.
Save nathanosoares/e3458b24cc4cd294d201ae959ef6105f to your computer and use it in GitHub Desktop.
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;
}
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