Reputation: 192
I want to build a scrollable widget with an int callback depending on where the user stopped scrolling. It is supposed to represent a scale for the user to select his weight.
This is what I want it to look like.
Upvotes: 2
Views: 460
Reputation: 1561
the basic idea is use scroll metrix we get from notification listener, flutter already have list builder lazyly render neccessary view, we can use that. no package need.
the code :
home.dart :
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
double? _userWeight;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
_userWeight == null
? 'User Weight : no data'
: 'User Weight : $_userWeight',
style: const TextStyle(fontSize: 24.0),
),
ScaleIndicator(
onScrollChanged: (double result) {},
onSelected: (double result) {
setState(() {
_userWeight = result;
});
},
),
],
),
),
);
}
}
scale indicator :
///callback when scrolling changed, since you ask for int
/// i prefer double instead : 20.5 kg or 100.34 kg
typedef OnScrollChanged = void Function(double scale);
///calback when selected
typedef OnSelected = void Function(double scale);
class ScaleIndicator extends StatefulWidget {
/// default value show in widget when first open
final double? initialValue;
///what the distance for performance, min max kg so the we can pass max length to listview builder
final int? range;
final Color? indicatorColor;
///callback when scrolling changed
final OnScrollChanged? onScrollChanged;
///calback when selected return double
final OnSelected onSelected;
const ScaleIndicator(
{Key? key,
this.initialValue,
this.range,
this.indicatorColor,
this.onScrollChanged,
required this.onSelected})
: super(key: key);
@override
State<ScaleIndicator> createState() => _ScaleIndicatorState();
}
class _ScaleIndicatorState extends State<ScaleIndicator> {
// default value show in widget when first open
late double _initialValue;
// what the distance for performance, min max kg so the we can pass max length to listview builder
late int _range;
late Color _indicatorColor;
late double _valueSelected;
static const double _indicatorWidth = 10.0;
@override
void initState() {
super.initState();
// set your default value here
_initialValue = 0;
_range = 200;
_indicatorColor = Colors.blue;
_valueSelected = 0;
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
double pixels = scroll.metrics.pixels;
double result = pixels / (_indicatorWidth * 10.0);
setState(() {
_valueSelected = result;
widget.onScrollChanged!(
double.tryParse(_valueSelected.toStringAsFixed(2)) ?? 0.0);
});
return true;
},
child: SizedBox(
width: 200,
height: 120,
child: Column(
children: [
Flexible(
child: FractionalTranslation(
translation: const Offset(0.175, 0.0),
child: Text(
"${_valueSelected.toStringAsFixed(2)} kg",
style: const TextStyle(
fontSize: 24.0, fontWeight: FontWeight.bold),
))),
Expanded(
child: ListView.builder(
itemCount: _range,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return SizedBox(
width: _indicatorWidth,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
width: 3,
height: _heightFromIndex(index),
decoration: BoxDecoration(
color: _indicatorColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(5.0),
topRight: Radius.circular(5.0))),
),
const Expanded(child: SizedBox())
],
),
);
},
),
),
//Button for ex you wanna show it on ShowDialog or else
Padding(
padding: const EdgeInsets.all(10.0),
child: ElevatedButton(
onPressed: () {
widget.onSelected(
double.tryParse(_valueSelected.toStringAsFixed(2)) ??
0.0);
},
child: const Text("Done")),
)
],
),
),
);
}
double _heightFromIndex(int index) {
if (index % 10 == 0) {
return 40.0;
} else if (index % 5 == 0) {
return 25.0;
}
return 10.0;
}
}
Upvotes: 5