feat: 0.6.0

see CHANGELOG.md
This commit is contained in:
Chen Asraf
2022-08-08 18:50:24 +03:00
parent 6029466325
commit 8f5e94fa51
6 changed files with 271 additions and 143 deletions

View File

@@ -1,3 +1,15 @@
## 0.6.0
- Use `WheelSpinnerTheme` and `WheelSpinnerThemeData` to define styles, similar to `Theme` and
`ThemeData` in Flutter
- Remove min/max labels and +/- symbols as they are too coupled to the widget which doesn't seem to
be in its' logical scope. It can be easily recreated and modified more extensively when done
manually.
- Remove height/width params - the widget adheres to its parent constraints, and since the labels
are not being built, the constraints can reliably set the size of the widget.
## 0.5.1
- Add example.dart

View File

@@ -18,12 +18,44 @@ event handlers. Here is a simple usage example:
Widget build(BuildContext context) {
return WheelSpinner(
value: value,
width: width,
min: 0.0,
max: 100.0,
borderRadius: borderRadius,
minMaxLabelBuilder: (value) => value,
onSlideUpdate: (val) => onChange(value),
);
}
```
## Customizing the theme
You can use the `theme` property to override a theme once, or wrap many sliders in the same
`WheelSpinnerTheme` widget, which references a theme in its' `data` property.
**Direct override example:**
```dart
WheelSpinner(
value: value,
min: 0.0,
max: 100.0,
onSlideUpdate: (val) => onChange(value),
theme: WheelSpinnerThemeData.light().copyWith(
borderRadius: BorderRadius.circular(10),
),
)
```
**Inherited widget override example:**
```dart
WheelSpinnerTheme(
data: WheelSpinnerThemeData.light().copyWith(
borderRadius: BorderRadius.circular(10),
),
child: WheelSpinner(
value: value,
min: 0.0,
max: 100.0,
onSlideUpdate: (val) => onChange(value),
),
)
```

View File

@@ -1,7 +1,6 @@
import 'dart:math';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/material.dart';
import 'package:wheel_spinner/wheel_spinner.dart';
class MyWidget extends StatefulWidget {
@@ -22,14 +21,23 @@ class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
final width = 100.0;
return WheelSpinner(
value: value,
width: width,
min: 0.0,
max: 100.0,
onSlideUpdate: (val) => setState(() => value = val),
return Center(
child: SizedBox(
width: 100,
height: 60,
child: WheelSpinnerTheme(
data: WheelSpinnerThemeData.light().copyWith(
borderRadius: BorderRadius.circular(10),
dividerColor: Colors.black,
),
child: WheelSpinner(
value: value,
min: 0.0,
max: 100.0,
onSlideUpdate: (val) => setState(() => value = val),
),
),
),
);
}
}

View File

@@ -3,6 +3,9 @@ library wheel_spinner;
import 'package:flutter/material.dart';
import 'package:wheel_spinner/utils.dart';
import 'package:wheel_spinner/wheel_spinner_theme.dart';
export 'wheel_spinner_theme.dart';
typedef ValueBuilder = Widget Function(double value);
typedef ValueStringBuilder = String Function(double value);
@@ -16,12 +19,6 @@ class WheelSpinner extends StatefulWidget {
/// Callback for when the user lets go of the slider
final Function(double value)? onSlideDone;
/// The widget [width]
final double width;
/// The widget [height]
final double height;
/// The initial [value] for the slider
final double value;
@@ -37,60 +34,26 @@ class WheelSpinner extends StatefulWidget {
/// Allows overriding the format of the left top and bottom labels for the [min]/[max] values
final ValueStringBuilder? minMaxLabelBuilder;
/// Allows to override style of labels
final TextStyle? labelStyle;
/// Override box decoration for the control
final BoxDecoration? boxDecoration;
/// Override box border for the control's [boxDecoration].
/// If [boxDecoration] is specified, it overrides this property.
final Border? border;
/// Override border radius for the control's [boxDecoration].
/// If [boxDecoration] is specified, it overrides this property.
final BorderRadius? borderRadius;
/// Override background color for the control's [boxDecoration].
/// If [boxDecoration] is specified, it overrides this property.
final Color? color;
/// Override background gradient for the control's [boxDecoration].
/// If [boxDecoration] is specified, it overrides this property.
final Gradient? gradient;
/// Amount of divisions to show on the knob
final int dividerCount;
/// Color of the lines dividing the control.
final Color? dividerColor;
/// The drag speed factor
final double _dragSpeedFactor = 1.0;
/// The theme for this wheel spinner
final WheelSpinnerThemeData? theme;
/// The default min/max label builder.
static ValueStringBuilder defaultMinMaxLabelBuilder = (v) => v.toStringAsFixed(2);
const WheelSpinner(
{Key? key,
this.onSlideUpdate,
this.onSlideDone,
this.width = 60,
this.height = 100,
this.min = double.negativeInfinity,
this.max = double.infinity,
this.value = 0.5,
this.childBuilder,
this.minMaxLabelBuilder,
this.labelStyle,
this.dividerCount = 10,
this.dividerColor,
this.boxDecoration,
this.border,
this.borderRadius,
this.color,
this.gradient})
: super(key: key);
const WheelSpinner({
Key? key,
this.onSlideUpdate,
this.onSlideDone,
this.min = double.negativeInfinity,
this.max = double.infinity,
this.value = 0.5,
this.childBuilder,
this.minMaxLabelBuilder,
this.theme,
}) : super(key: key);
@override
// ignore: library_private_types_in_public_api
@@ -115,57 +78,61 @@ class _WheelSpinnerState extends State<WheelSpinner> with SingleTickerProviderSt
super.initState();
}
WheelSpinnerThemeData get theme => defaultTheme.copyWith(
border: widget.theme?.border,
borderRadius: widget.theme?.borderRadius,
color: widget.theme?.color,
gradient: widget.theme?.gradient,
boxDecoration: widget.theme?.boxDecoration,
dividerCount: widget.theme?.dividerCount,
dividerColor: widget.theme?.dividerColor,
);
WheelSpinnerThemeData get defaultTheme =>
WheelSpinnerTheme.of(context) ??
(Theme.of(context).brightness == Brightness.light
? WheelSpinnerThemeData.light()
: WheelSpinnerThemeData.dark());
@override
Widget build(BuildContext context) {
final minMaxBuilder = widget.minMaxLabelBuilder ?? WheelSpinner.defaultMinMaxLabelBuilder;
final labelFontSize = Theme.of(context).textTheme.bodyText2!.fontSize! * 0.75;
final labelStyle = TextStyle(fontSize: labelFontSize).merge(widget.labelStyle);
final minText = widget.max < double.infinity ? minMaxBuilder(widget.max) : null;
final maxText = widget.min > double.negativeInfinity ? minMaxBuilder(widget.min) : null;
return SizedBox(
height: widget.height,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
minText != null ? Text(minText, style: labelStyle) : Container(),
maxText != null ? Text(maxText, style: labelStyle) : Container(),
],
),
),
),
AnimatedBuilder(
animation: flingAnimation,
builder: (context, child) => GestureDetector(
onVerticalDragStart: onDragStart,
onVerticalDragUpdate: onDragUpdate,
onVerticalDragEnd: onDragDone,
child: SizedBox.fromSize(
size: Size(widget.width.toDouble(), widget.height.toDouble()),
return LayoutBuilder(builder: (context, constraints) {
debugPrint('borderRadius: ${theme.borderRadius}');
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedBuilder(
animation: flingAnimation,
builder: (context, child) => GestureDetector(
onVerticalDragStart: onDragStart,
onVerticalDragUpdate: onDragUpdate,
onVerticalDragEnd: onDragDone,
child: Container(
decoration: widget.boxDecoration ?? _boxDecorationBuilder(),
width: constraints.maxWidth,
height: constraints.maxHeight,
decoration: theme.boxDecoration ?? _boxDecorationBuilder(),
child: Stack(
children: List<Widget>.generate(
widget.dividerCount + 1,
theme.dividerCount + 1,
(i) {
final top = lineTopPos(value, i, flingAnimation.value);
final top = lineTopPos(
value,
i,
flingAnimation.value,
constraints.maxHeight,
);
return Positioned.fromRect(
rect: Rect.fromLTWH(
0.0,
top,
widget.width.toDouble(),
constraints.maxWidth,
0,
),
child: Divider(
color: widget.dividerColor ?? Colors.grey[600],
color: theme.dividerColor,
),
);
},
@@ -175,50 +142,24 @@ class _WheelSpinnerState extends State<WheelSpinner> with SingleTickerProviderSt
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('+', style: labelStyle),
Text('-', style: labelStyle),
],
),
),
),
],
),
);
],
),
);
});
}
BoxDecoration _boxDecorationBuilder() {
const shadowOffset = 0.2;
return BoxDecoration(
gradient: (widget.gradient ?? widget.color) == null
? LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, shadowOffset, 1.0 - shadowOffset, 1.0],
colors: [Colors.grey[350]!, Colors.grey[50]!, Colors.grey[50]!, Colors.grey[350]!],
)
: null,
color: widget.color,
border: widget.border ??
Border.all(
width: 1,
style: BorderStyle.solid,
color: Colors.grey[600]!,
),
borderRadius: widget.borderRadius ?? BorderRadius.circular(3.5),
gradient: theme.gradient,
color: theme.color,
border: theme.border,
borderRadius: theme.borderRadius,
);
}
double lineTopPos(double value, int i, double fling) {
final valueFraction = (value.ceil() - value) * widget.dividerCount;
final indexedTop = (widget.height / widget.dividerCount * i);
double lineTopPos(double value, int i, double fling, double maxHeight) {
final valueFraction = (value.ceil() - value) * theme.dividerCount;
final indexedTop = (maxHeight / theme.dividerCount * i);
final top = indexedTop + valueFraction;
return top;
}

View File

@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
/// The theme for the wheel spinner
/// You can use the constructor [WheelSpinnerThemeData] to create your own theme,
/// or use [WheelSpinnerThemeData.light()] or [WheelSpinnerThemeData.dark()] and then use [copyWith] to
/// override only specifics.
class WheelSpinnerThemeData {
/// Override box decoration for the control
final BoxDecoration? boxDecoration;
/// Override box border for the control's [boxDecoration].
/// If [boxDecoration] is specified, this is ignored.
final Border? border;
/// Override border radius for the control's [boxDecoration].
/// If [boxDecoration] is specified, this is ignored.
final BorderRadius? borderRadius;
/// Override background color for the control's [boxDecoration].
/// If [boxDecoration] or [gradient] is specified, this is ignored.
final Color? color;
/// Override background gradient for the control's [boxDecoration].
/// If [boxDecoration] is specified, this is ignored.
final Gradient? gradient;
/// Amount of lines dividing the control. Defaults to 10.
final int dividerCount;
/// Color of the lines dividing the control.
final Color? dividerColor;
/// Create a new theme for the wheel spinner
WheelSpinnerThemeData({
this.boxDecoration,
this.border,
this.borderRadius = defaultBorderRadius,
this.color,
this.gradient,
this.dividerCount = 10,
this.dividerColor,
});
/// default shadow offset for both light+dark themes
static const double defaultShadowOffset = 0.2;
/// default border radius for both light+dark themes
static const BorderRadius defaultBorderRadius = BorderRadius.all(Radius.circular(8));
/// A default light theme
WheelSpinnerThemeData.light()
: dividerColor = Colors.grey[600],
dividerCount = 10,
border = Border.all(
width: 1,
style: BorderStyle.solid,
color: Colors.grey[600]!,
),
gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, defaultShadowOffset, 1.0 - defaultShadowOffset, 1.0],
colors: [Colors.grey[350]!, Colors.grey[50]!, Colors.grey[50]!, Colors.grey[350]!],
),
borderRadius = defaultBorderRadius,
boxDecoration = null,
color = null;
/// A default dark theme
WheelSpinnerThemeData.dark()
: dividerColor = Colors.grey[800],
dividerCount = 10,
border = Border.all(
width: 1,
style: BorderStyle.solid,
color: Colors.grey[600]!,
),
gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, defaultShadowOffset, 1 - defaultShadowOffset, 1.0],
colors: [Colors.black, Colors.grey[900]!, Colors.grey[900]!, Colors.black],
),
borderRadius = defaultBorderRadius,
boxDecoration = null,
color = null;
/// Create a new theme based on this one, but with the given properties overridden.
/// For each property that is null, the original value is used.
WheelSpinnerThemeData copyWith({
BoxDecoration? boxDecoration,
Border? border,
BorderRadius? borderRadius,
Color? color,
Gradient? gradient,
int? dividerCount,
Color? dividerColor,
}) =>
WheelSpinnerThemeData(
boxDecoration: boxDecoration ?? this.boxDecoration,
border: border ?? this.border,
borderRadius: borderRadius ?? this.borderRadius,
color: color ?? this.color,
gradient: gradient ?? this.gradient,
dividerCount: dividerCount ?? this.dividerCount,
dividerColor: dividerColor ?? this.dividerColor,
);
}
/// Theme container widget for the wheel spinner theme.
///
/// Any wheel spinners below this widget in the tree will
/// inherit the theme given in [data], except when overridden manually as a property of the wheel spinner itself.
class WheelSpinnerTheme extends InheritedWidget {
final WheelSpinnerThemeData data;
const WheelSpinnerTheme({
super.key,
required this.data,
required super.child,
});
@override
bool updateShouldNotify(covariant WheelSpinnerTheme oldWidget) => oldWidget.data != data;
/// Get the nearest wheel spinner theme data up the widget tree from the given context.
static WheelSpinnerThemeData? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<WheelSpinnerTheme>()?.data;
}

View File

@@ -1,7 +1,8 @@
name: wheel_spinner
description: A simple Flutter widget for updating a number using a pitch bender-like spinner
version: 0.5.1
version: 0.6.0
homepage: https://github.com/chenasraf/wheel_spinner
repository: https://github.com/chenasraf/wheel_spinner
environment:
sdk: '>=2.17.0 <3.0.0'
@@ -51,3 +52,8 @@ flutter:
#
# For details regarding fonts in packages, see
# https://flutter.io/custom-fonts/#from-packages
script_runner:
shell: /bin/zsh
scripts:
- doc: flutter pub global run dartdoc:dartdoc