Last active
September 4, 2018 07:06
-
-
Save MarcinusX/d6b8869652243d0a53ea7f169a8c69b3 to your computer and use it in GitHub Desktop.
BMI Calculator - Weight
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'; | |
const TextStyle _titleStyle = TextStyle( | |
fontSize: 16.0, | |
fontWeight: FontWeight.w500, | |
color: Color.fromRGBO(14, 24, 35, 1.0), | |
); | |
const TextStyle _subtitleStyle = TextStyle( | |
fontSize: 8.0, | |
fontWeight: FontWeight.w500, | |
color: Color.fromRGBO(78, 102, 114, 1.0), | |
); | |
class CardTitle extends StatelessWidget { | |
final String title; | |
final String subtitle; | |
const CardTitle(this.title, {Key key, this.subtitle}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return RichText( | |
text: TextSpan( | |
text: title, | |
style: _titleStyle, | |
children: <TextSpan>[ | |
TextSpan( | |
text: subtitle ?? "", | |
style: _subtitleStyle, | |
), | |
], | |
), | |
); | |
} | |
} |
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:bmi_calculator/card_title.dart'; | |
import 'package:bmi_calculator/weight_slider.dart'; | |
import 'package:bmi_calculator/widget_utils.dart' show screenAwareSize; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_svg/flutter_svg.dart'; | |
class WeightCard extends StatefulWidget { | |
final int initialWeight; | |
const WeightCard({Key key, this.initialWeight}) : super(key: key); | |
@override | |
_WeightCardState createState() => _WeightCardState(); | |
} | |
class _WeightCardState extends State<WeightCard> { | |
int weight; | |
@override | |
void initState() { | |
super.initState(); | |
weight = widget.initialWeight ?? 70; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Card( | |
child: Padding( | |
padding: EdgeInsets.only(top: screenAwareSize(32.0, context)), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
CardTitle("WEIGHT", subtitle: "(KG)"), | |
Expanded( | |
child: Center( | |
child: Padding( | |
padding: EdgeInsets.symmetric( | |
horizontal: screenAwareSize(16.0, context)), | |
child: _drawSlider(), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
Widget _drawSlider() { | |
return WeightBackground( | |
child: LayoutBuilder( | |
builder: (context, constraints) { | |
return constraints.isTight | |
? Container() | |
: WeightSlider( | |
minValue: 30, | |
maxValue: 110, | |
value: weight, | |
onChanged: (val) => setState(() => weight = val), | |
width: constraints.maxWidth, | |
); | |
}, | |
), | |
); | |
} | |
} | |
class WeightBackground extends StatelessWidget { | |
final Widget child; | |
const WeightBackground({Key key, this.child}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Stack( | |
alignment: Alignment.bottomCenter, | |
children: <Widget>[ | |
Container( | |
height: screenAwareSize(100.0, context), | |
decoration: BoxDecoration( | |
color: Color.fromRGBO(244, 244, 244, 1.0), | |
borderRadius: | |
new BorderRadius.circular(screenAwareSize(50.0, context)), | |
), | |
child: child, | |
), | |
SvgPicture.asset( | |
"images/weight_arrow.svg", | |
height: screenAwareSize(10.0, context), | |
width: screenAwareSize(18.0, context), | |
), | |
], | |
); | |
} | |
} |
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' as math; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
class WeightSlider extends StatelessWidget { | |
WeightSlider({ | |
Key key, | |
@required this.minValue, | |
@required this.maxValue, | |
@required this.value, | |
@required this.onChanged, | |
@required this.width, | |
}) : scrollController = new ScrollController( | |
initialScrollOffset: (value - minValue) * width / 3, | |
), | |
super(key: key); | |
final int minValue; | |
final int maxValue; | |
final int value; | |
final ValueChanged<int> onChanged; | |
final double width; | |
final ScrollController scrollController; | |
double get itemExtent => width / 3; | |
int _indexToValue(int index) => minValue + (index - 1); | |
@override | |
build(BuildContext context) { | |
int itemCount = (maxValue - minValue) + 3; | |
return NotificationListener( | |
onNotification: _onNotification, | |
child: new ListView.builder( | |
controller: scrollController, | |
scrollDirection: Axis.horizontal, | |
itemExtent: itemExtent, | |
itemCount: itemCount, | |
physics: BouncingScrollPhysics(), | |
itemBuilder: (BuildContext context, int index) { | |
int itemValue = _indexToValue(index); | |
bool isExtra = index == 0 || index == itemCount - 1; | |
return isExtra | |
? new Container() //empty first and last element | |
: GestureDetector( | |
behavior: HitTestBehavior.translucent, | |
onTap: () => _animateTo(itemValue, durationMillis: 50), | |
child: FittedBox( | |
child: Text( | |
itemValue.toString(), | |
style: _getTextStyle(itemValue), | |
), | |
fit: BoxFit.scaleDown, | |
), | |
); | |
}, | |
), | |
); | |
} | |
TextStyle _getDefaultTextStyle() { | |
return new TextStyle( | |
color: Color.fromRGBO(196, 197, 203, 1.0), | |
fontSize: 14.0, | |
); | |
} | |
TextStyle _getHighlightTextStyle() { | |
return new TextStyle( | |
color: Color.fromRGBO(77, 123, 243, 1.0), | |
fontSize: 28.0, | |
); | |
} | |
TextStyle _getTextStyle(int itemValue) { | |
return itemValue == value | |
? _getHighlightTextStyle() | |
: _getDefaultTextStyle(); | |
} | |
bool _userStoppedScrolling(Notification notification) { | |
return notification is UserScrollNotification && | |
notification.direction == ScrollDirection.idle && | |
scrollController.position.activity is! HoldScrollActivity; | |
} | |
_animateTo(int valueToSelect, {int durationMillis = 200}) { | |
double targetExtent = (valueToSelect - minValue) * itemExtent; | |
scrollController.animateTo( | |
targetExtent, | |
duration: new Duration(milliseconds: durationMillis), | |
curve: Curves.decelerate, | |
); | |
} | |
int _offsetToMiddleIndex(double offset) => (offset + width / 2) ~/ itemExtent; | |
int _offsetToMiddleValue(double offset) { | |
int indexOfMiddleElement = _offsetToMiddleIndex(offset); | |
int middleValue = _indexToValue(indexOfMiddleElement); | |
middleValue = math.max(minValue, math.min(maxValue, middleValue)); | |
return middleValue; | |
} | |
bool _onNotification(Notification notification) { | |
if (notification is ScrollNotification) { | |
int middleValue = _offsetToMiddleValue(notification.metrics.pixels); | |
if (_userStoppedScrolling(notification)) { | |
_animateTo(middleValue); | |
} | |
if (middleValue != value) { | |
onChanged(middleValue); //update selection | |
} | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment