mirror of
https://github.com/chenasraf/wheel_spinner.git
synced 2026-05-17 17:48:00 +00:00
feat: 0.6.0
see CHANGELOG.md
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -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
|
||||
|
||||
38
README.md
38
README.md
@@ -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),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
129
lib/wheel_spinner_theme.dart
Normal file
129
lib/wheel_spinner_theme.dart
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user