Skip to content

Instantly share code, notes, and snippets.

@MarcinusX
Last active April 14, 2020 05:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MarcinusX/2cbef7d9c16acee7007ea72872107433 to your computer and use it in GitHub Desktop.
Save MarcinusX/2cbef7d9c16acee7007ea72872107433 to your computer and use it in GitHub Desktop.
https://marcinszalek.pl/tag/bmi-calculator/
import 'package:bmi_calculator/card_title.dart';
import 'package:bmi_calculator/height/height_picker.dart';
import 'package:bmi_calculator/widget_utils.dart';
import 'package:flutter/material.dart';
class HeightCard extends StatefulWidget {
final int height;
const HeightCard({Key key, this.height}) : super(key: key);
@override
HeightCardState createState() => HeightCardState();
}
class HeightCardState extends State<HeightCard> {
int height;
@override
void initState() {
super.initState();
height = widget.height ?? 170;
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.only(top: screenAwareSize(16.0, context)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CardTitle("HEIGHT", subtitle: "(cm)"),
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: screenAwareSize(8.0, context)),
child: LayoutBuilder(builder: (context, constraints) {
return HeightPicker(
widgetHeight: constraints.maxHeight,
height: height,
onChange: (val) => setState(() => height = val),
);
}),
),
),
],
),
),
);
}
}
import 'dart:math' as math;
import 'package:bmi_calculator/height/height_slider.dart';
import 'package:bmi_calculator/height/height_styles.dart';
import 'package:bmi_calculator/widget_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class HeightPicker extends StatefulWidget {
final int maxHeight;
final int minHeight;
final int height;
final double widgetHeight;
final ValueChanged<int> onChange;
const HeightPicker(
{Key key,
this.height,
this.widgetHeight,
this.onChange,
this.maxHeight = 190,
this.minHeight = 145})
: super(key: key);
int get totalUnits => maxHeight - minHeight;
@override
_HeightPickerState createState() => _HeightPickerState();
}
class _HeightPickerState extends State<HeightPicker> {
double startDragYOffset;
int startDragHeight;
double get _pixelsPerUnit {
return _drawingHeight / widget.totalUnits;
}
double get _sliderPosition {
double halfOfBottomLabel = labelsFontSize / 2;
int unitsFromBottom = widget.height - widget.minHeight;
return halfOfBottomLabel + unitsFromBottom * _pixelsPerUnit;
}
///returns actual height of slider to be able to slide
double get _drawingHeight {
double totalHeight = widget.widgetHeight;
double marginBottom = marginBottomAdapted(context);
double marginTop = marginTopAdapted(context);
return totalHeight - (marginBottom + marginTop + labelsFontSize);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTapDown: _onTapDown,
onVerticalDragStart: _onDragStart,
onVerticalDragUpdate: _onDragUpdate,
child: Stack(
children: <Widget>[
_drawPersonImage(),
_drawSlider(),
_drawLabels(),
],
),
);
}
_onTapDown(TapDownDetails tapDownDetails) {
int height = _globalOffsetToHeight(tapDownDetails.globalPosition);
widget.onChange(_normalizeHeight(height));
}
int _normalizeHeight(int height) {
return math.max(widget.minHeight, math.min(widget.maxHeight, height));
}
int _globalOffsetToHeight(Offset globalOffset) {
RenderBox getBox = context.findRenderObject();
Offset localPosition = getBox.globalToLocal(globalOffset);
double dy = localPosition.dy;
dy = dy - marginTopAdapted(context) - labelsFontSize / 2;
int height = widget.maxHeight - (dy ~/ _pixelsPerUnit);
return height;
}
_onDragStart(DragStartDetails dragStartDetails) {
int newHeight = _globalOffsetToHeight(dragStartDetails.globalPosition);
widget.onChange(newHeight);
setState(() {
startDragYOffset = dragStartDetails.globalPosition.dy;
startDragHeight = newHeight;
});
}
_onDragUpdate(DragUpdateDetails dragUpdateDetails) {
double currentYOffset = dragUpdateDetails.globalPosition.dy;
double verticalDifference = startDragYOffset - currentYOffset;
int diffHeight = verticalDifference ~/ _pixelsPerUnit;
int height = _normalizeHeight(startDragHeight + diffHeight);
setState(() => widget.onChange(height));
}
Widget _drawSlider() {
return Positioned(
child: HeightSlider(height: widget.height),
left: 0.0,
right: 0.0,
bottom: _sliderPosition,
);
}
Widget _drawLabels() {
int labelsToDisplay = widget.totalUnits ~/ 5 + 1;
List<Widget> labels = List.generate(
labelsToDisplay,
(idx) {
return Text(
"${widget.maxHeight - 5 * idx}",
style: labelsTextStyle,
);
},
);
return Align(
alignment: Alignment.centerRight,
child: IgnorePointer(
child: Padding(
padding: EdgeInsets.only(
right: screenAwareSize(12.0, context),
bottom: marginBottomAdapted(context),
top: marginTopAdapted(context),
),
child: Column(
children: labels,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
),
),
);
}
Widget _drawPersonImage() {
double personImageHeight = _sliderPosition + marginBottomAdapted(context);
return Align(
alignment: Alignment.bottomCenter,
child: SvgPicture.asset(
"images/person.svg",
height: personImageHeight,
width: personImageHeight / 3,
),
);
}
}
import 'package:bmi_calculator/height/height_styles.dart';
import 'package:bmi_calculator/widget_utils.dart';
import 'package:flutter/material.dart';
class HeightSlider extends StatelessWidget {
final int height;
const HeightSlider({Key key, this.height}) : super(key: key);
@override
Widget build(BuildContext context) {
return IgnorePointer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SliderLabel(height: height),
Row(
children: <Widget>[
SliderCircle(),
Expanded(child: SliderLine()),
],
),
],
),
);
}
}
class SliderLabel extends StatelessWidget {
final int height;
const SliderLabel({Key key, this.height}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: screenAwareSize(4.0, context),
bottom: screenAwareSize(2.0, context),
),
child: Text(
"$height",
style: TextStyle(
fontSize: selectedLabelFontSize,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w600,
),
),
);
}
}
class SliderLine extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: List.generate(
40,
(i) => Expanded(
child: Container(
height: 2.0,
decoration: BoxDecoration(
color: i.isEven
? Theme.of(context).primaryColor
: Colors.white),
),
)),
);
}
}
class SliderCircle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: circleSizeAdapted(context),
height: circleSizeAdapted(context),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
),
child: Icon(
Icons.unfold_more,
color: Colors.white,
size: 0.6 * circleSizeAdapted(context),
),
);
}
}
import 'package:bmi_calculator/widget_utils.dart';
import 'package:flutter/material.dart';
export 'package:bmi_calculator/styles.dart';
double marginBottomAdapted(BuildContext context) =>
screenAwareSize(marginBottom, context);
double marginTopAdapted(BuildContext context) =>
screenAwareSize(marginTop, context);
double circleSizeAdapted(BuildContext context) =>
screenAwareSize(circleSize, context);
const TextStyle labelsTextStyle = const TextStyle(
color: labelsGrey,
fontSize: labelsFontSize,
);
const double circleSize = 32.0;
const double marginBottom = 16.0;
const double marginTop = 26.0;
const double selectedLabelFontSize = 14.0;
const double labelsFontSize = 13.0;
const Color labelsGrey = const Color.fromRGBO(216, 217, 223, 1.0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment