diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc6ddb..36534a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,10 +52,14 @@ * `selectByTap` feature added * some options ( values, max, min, step, ... ) can be updated by setState -## [2.3.1] - 03/30/2019 +## [2.3.1] - 04/04/2019 * removed TouchZone * `leftInactiveTrackBar` and `rightInactiveTrackBar` changed to `inactiveTrackBar` * each handler can now be disabled * trackbar disabled color feature added -* More accurate handlers movement \ No newline at end of file +* More accurate handlers movement + +## [2.4.0] - 04/05/2019 + +* hatchMark feature added \ No newline at end of file diff --git a/README.md b/README.md index b27f79e..f1fae52 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ FlutterSlider( ![](images/single.gif) -to make slider `Right To Left` use `rtl: true` +To make slider `Right To Left` use `rtl: true` ```dart FlutterSlider( @@ -129,7 +129,7 @@ FlutterSlider( ## Trackbars -to customize track bars you can use `FlutterSliderTrackBar`. [You can see the details here](https://pub.dartlang.org/documentation/flutter_xlider/latest/flutter_xlider/FlutterSliderTrackBar/FlutterSliderTrackBar.html) +To customize track bars you can use `FlutterSliderTrackBar`. [You can see the details here](https://pub.dartlang.org/documentation/flutter_xlider/latest/flutter_xlider/FlutterSliderTrackBar/FlutterSliderTrackBar.html) ```dart FlutterSlider( @@ -145,7 +145,7 @@ FlutterSlider( ## Tooltips -in order to customize your tooltips, you can use `FlutterSliderTooltip` class. [You can see all properties here](https://pub.dartlang.org/documentation/flutter_xlider/latest/flutter_xlider/FlutterSliderTooltip/FlutterSliderTooltip.html) +In order to customize your tooltips, you can use `FlutterSliderTooltip` class. [You can see all properties here](https://pub.dartlang.org/documentation/flutter_xlider/latest/flutter_xlider/FlutterSliderTooltip/FlutterSliderTooltip.html) ```dart FlutterSlider( @@ -186,7 +186,7 @@ FlutterSlider( ### Tooltip Number Format -you can customize tooltip numbers by using `NumberFormat` class +You can customize tooltip numbers by using `NumberFormat` class here is an example ```dart @@ -199,7 +199,7 @@ FlutterSlider( ... ) ``` -you can find more about [NumberFormat](https://docs.flutter.io/flutter/intl/NumberFormat-class.html) +You can find more about [NumberFormat](https://docs.flutter.io/flutter/intl/NumberFormat-class.html) ![](images/range-compact.gif) @@ -220,7 +220,7 @@ FlutterSlider( ### Always Show Tooltips -tooltips always displayed if this property is set to `true`. +Tooltips always displayed if this property is set to `true`. ```dart FlutterSlider( @@ -269,7 +269,7 @@ FlutterSlider( ### Jump -by default slider handlers move fluently, if you set `jump` to true, handlers will jump between intervals +By default slider handlers move fluently, if you set `jump` to true, handlers will jump between intervals ```dart FlutterSlider( @@ -293,7 +293,7 @@ FlutterSlider( ### Ignore Steps -if your configurations requires that some steps are not available, you can use `ignoreSteps` property. +If your configurations requires that some steps are not available, you can use `ignoreSteps` property. this property accepts a simple class to define `from` and `to` ranges. ```dart @@ -311,7 +311,7 @@ FlutterSlider( ### Minimum Distance -when using range slider, the minimum distance between two handlers can be defined using `minimumDistance` option +When using range slider, the minimum distance between two handlers can be defined using `minimumDistance` option ```dart FlutterSlider( @@ -325,7 +325,7 @@ FlutterSlider( ### Maximum Distance -this is the opposite of minimum distance, when using range slider, the maximum distance between two handlers can be defined using `maximumDistance` option +This is the opposite of minimum distance, when using range slider, the maximum distance between two handlers can be defined using `maximumDistance` option ```dart FlutterSlider( @@ -338,7 +338,41 @@ FlutterSlider( ![](images/range-maximum-distance.gif) +### Hatch Mark +You can display a `Hatch Mark` underneath or beside of your slider based on `axis`. In order to display hatch mark you should +use `FlutterSliderHatchMark` class which has following properties: + +1. `distanceFromTrackBar`: The distance between slider and hatch mark +2. `density`: The amount of lines per percent. 1 is default. any number less or more than 1 will decrease and increase lines respectively +3. `labels`: If you want to display some label or text at certain percent in your hatch mark, you can use `labels` +4. `smallLine`: The widget of small lines in hatch mark +5. `bigLine`: The widget of big lines in hatch mark +6. `labelBox`: The widget of label box + +Here is an example: + +```dart +FlutterSlider( + ... + hatchMark: FlutterSliderHatchMark( + distanceFromTrackBar: 10, + density: 0.5, + labels: [ + FlutterSliderHatchMarkLabel(percent: 0, label: 'Start'), + FlutterSliderHatchMarkLabel(percent: 10, label: '10,000'), + FlutterSliderHatchMarkLabel(percent: 50, label: '50 %'), + FlutterSliderHatchMarkLabel(percent: 80, label: '80,000'), + FlutterSliderHatchMarkLabel(percent: 100, label: 'Finish'), + ], + ), + ... +) +``` + +![](images/hatch-mark.gif) + +**You MUST define with or height for the parent container of your slider to display `hatchMark` properly.** ### ~~Touch Zone~~ diff --git a/images/hatch-mark.gif b/images/hatch-mark.gif new file mode 100644 index 0000000..67d71cb Binary files /dev/null and b/images/hatch-mark.gif differ diff --git a/lib/flutter_xlider.dart b/lib/flutter_xlider.dart index 1812a55..a8edaa5 100644 --- a/lib/flutter_xlider.dart +++ b/lib/flutter_xlider.dart @@ -46,6 +46,7 @@ class FlutterSlider extends StatefulWidget { final FlutterSliderTooltip tooltip; final FlutterSliderTrackBar trackBar; final double step; + final FlutterSliderHatchMark hatchMark; FlutterSlider( {this.key, @@ -73,7 +74,8 @@ class FlutterSlider extends StatefulWidget { this.trackBar = const FlutterSliderTrackBar(), this.handlerAnimation = const FlutterSliderHandlerAnimation(), this.selectByTap = true, - this.step = 1}) + this.step = 1, + this.hatchMark}) : assert(touchSize == null || (touchSize != null && (touchSize >= 10 && touchSize <= 100))), assert(values != null), @@ -163,6 +165,8 @@ class _FlutterSliderState extends State double xDragStart; double yDragStart; + List _points = []; + double __dAxis, __rAxis, __axisDragTmp, @@ -209,6 +213,15 @@ class _FlutterSliderState extends State _arrangeHandlersZIndex(); +// _plusSpinnerStyle = widget.plusButton ?? SpinnerButtonStyle(); +// _plusSpinnerStyle.child ??= Icon(Icons.add, size: 16); + +// if (widget.divisions != null) { +// _divisions = widget.divisions; +// } else { +// _divisions = (_fakeMax / 1000) < 1000 ? _fakeMax : (_fakeMax / 1000); +// } + _originalToolTipData = _tooltipData.toString(); _generateHandler(); @@ -299,6 +312,11 @@ class _FlutterSliderState extends State _finalLeftHandlerHeight = _handlersHeight; _finalRightHandlerHeight = _handlersHeight; +// if(widget.axis == Axis.vertical) { +// animationStart = Offset(0.20, 0); +// animationFinish = Offset(-0.52, 0); +// } + _leftHandlerScaleAnimationController = AnimationController( duration: widget.handlerAnimation.duration, vsync: this); _rightHandlerScaleAnimationController = AnimationController( @@ -379,9 +397,120 @@ class _FlutterSliderState extends State _arrangeHandlersPosition(); + _drawHatchMark(); + setState(() {}); } + void _drawHatchMark() { + if (widget.hatchMark == null || widget.hatchMark.disabled) return; + + FlutterSliderHatchMark hatchMark = + widget.hatchMark ?? FlutterSliderHatchMark(); + hatchMark.density ??= 1; + hatchMark.distanceFromTrackBar ??= 8; + hatchMark.labelTextStyle ??= TextStyle(fontSize: 12); + hatchMark.smallLine ??= FlutterSliderSizedBox( + height: 5, width: 1, decoration: BoxDecoration(color: Colors.black45)); + hatchMark.bigLine ??= FlutterSliderSizedBox( + height: 9, width: 2, decoration: BoxDecoration(color: Colors.black45)); + hatchMark.labelBox ??= FlutterSliderSizedBox(height: 35, width: 50); + + double percent = 100 * hatchMark.density; + double top, left, barWidth, barHeight, distance; + + if (widget.axis == Axis.horizontal) { + top = _constraintMaxHeight / 2 + hatchMark.distanceFromTrackBar; + distance = ((_constraintMaxWidth - _handlersWidth) / percent); + } else { + left = _constraintMaxWidth / 2 + hatchMark.distanceFromTrackBar; + distance = ((_constraintMaxHeight - _handlersHeight) / percent); + } + + for (int p = 0; p <= percent; p++) { + String label = ''; + FlutterSliderSizedBox barLineBox = hatchMark.smallLine; + Widget barLine; + double labelBoxHalfSize = 0; + + List labelWidget = []; + if (widget.hatchMark.labels.length > 0) { + for (FlutterSliderHatchMarkLabel markLabel in widget.hatchMark.labels) { + double tr = markLabel.percent; + if (widget.rtl) tr = 100 - tr; + if (tr * hatchMark.density == p) { + label = markLabel.label; + + barLineBox = hatchMark.bigLine; + + if (widget.axis == Axis.horizontal) { + labelBoxHalfSize = hatchMark.labelBox.width / 2 - 0.5; + } else { + labelBoxHalfSize = hatchMark.labelBox.height / 2 - 0.5; + } + + labelWidget = [ + SizedBox( + width: 2, + height: 2, + ), + Container( + height: hatchMark.labelBox.height, + width: hatchMark.labelBox.width, + decoration: hatchMark.labelBox.decoration, + foregroundDecoration: hatchMark.labelBox.foregroundDecoration, + transform: hatchMark.labelBox.transform, + child: Center( + child: Text( + label, + style: hatchMark.labelTextStyle, + maxLines: 5, + overflow: TextOverflow.visible, + textAlign: TextAlign.center, + )), + ) + ]; + + break; + } + } + } + + if (widget.axis == Axis.horizontal) { + barHeight = barLineBox.height; + barWidth = barLineBox.width; + } else { + barHeight = barLineBox.width; + barWidth = barLineBox.height; + } + + barLine = Container( + decoration: barLineBox.decoration, + foregroundDecoration: barLineBox.foregroundDecoration, + transform: barLineBox.transform, + height: barHeight, + width: barWidth, + ); + + List barContents = [barLine]..addAll(labelWidget); + + Widget bar; + if (widget.axis == Axis.horizontal) { + bar = Column( + children: barContents, + ); + left = (p * distance) + _handlersPadding - labelBoxHalfSize; + } else { + bar = Row( + children: barContents, + ); + top = (p * distance) + _handlersPadding - labelBoxHalfSize; + } + + _points.add(Positioned(top: top, left: left, child: bar)); + } + } + @override void didUpdateWidget(FlutterSlider oldWidget) { bool lowerValueEquality = oldWidget.values[0] != widget.values[0]; @@ -397,6 +526,9 @@ class _FlutterSliderState extends State _originalToolTipData != widget.tooltip.toString()) { bool reArrangePositions = false; +// if(_originalMin != widget.min || _originalMax != widget.max) +// reArrangePositions = true; + _setParameters(); if (lowerValueEquality || upperValueEquality) { @@ -590,20 +722,15 @@ class _FlutterSliderState extends State if (tmpLowerValue > _upperValue) tmpLowerValue = _upperValue; if (widget.jump == true) { + double handlerOrTouch = + (__handlerSize > _touchSize) ? __handlerSize : _touchSize; + double des = ((__containerSizeWithoutHandlerSize / _fakeMax) * tmpLowerValue) - (_touchSize); - if (__middle <= - des + - ((__handlerSize > _touchSize) - ? __handlerSize - : _touchSize) && - (__middle >= - des - - ((__handlerSize > _touchSize) - ? __handlerSize - : _touchSize))) { + if (__middle <= des + (handlerOrTouch + handlerOrTouch / 2) && + (__middle >= des - (handlerOrTouch + handlerOrTouch / 2))) { _lowerValue = tmpLowerValue; __leftHandlerPosition = des; @@ -713,9 +840,17 @@ class _FlutterSliderState extends State } } + /* + * if (validMove && + __axisPosTmp - (_touchSize) <= __rightHandlerPosition + 1 && + __axisPosTmp + _handlersPadding >= + _handlersPadding - 1 /* - _leftPadding*/ + ) + * */ + if (validMove && - __axisPosTmp >= __leftHandlerPosition - 1 + (_touchSize) && - __axisPosTmp + _handlersPadding <= __containerSizeWithoutHalfPadding) { + __axisPosTmp + (_touchSize) >= __leftHandlerPosition - 1 && + __axisPosTmp - _handlersWidth <= __containerSizeWithoutHalfPadding) { double tmpUpperValue = __rAxis; if (tmpUpperValue > _fakeMax) tmpUpperValue = _fakeMax; @@ -724,34 +859,21 @@ class _FlutterSliderState extends State if (tmpUpperValue < _lowerValue) tmpUpperValue = _lowerValue; if (widget.jump == true) { + double handlerOrTouch = + (__handlerSize > _touchSize) ? __handlerSize : _touchSize; + double des = ((__containerSizeWithoutHandlerSize / _fakeMax) * tmpUpperValue) - (_touchSize); // drag from right to left - if (__middle <= - des + - ((__handlerSize > _touchSize) - ? __handlerSize - : _touchSize) && - (__middle >= - des - - ((__handlerSize > _touchSize) - ? __handlerSize - : _touchSize))) { + if (__middle <= des + (handlerOrTouch + handlerOrTouch / 2) && + (__middle >= des - (handlerOrTouch + handlerOrTouch / 2))) { _upperValue = tmpUpperValue; __rightHandlerPosition = des; _updateUpperValue(tmpUpperValue); } - -/* else if (__dragStartPoint < __dAxis) { - - _upperValue = tmpUpperValue; - __rightHandlerPosition = des; - - _updateUpperValue(tmpUpperValue); - }*/ } else { _upperValue = tmpUpperValue; @@ -964,70 +1086,75 @@ class _FlutterSliderState extends State } drawHandlers() { - List items = [ - Function.apply(_inactiveTrack, []), - Function.apply(_activeTrack, []), - Positioned( - left: 0, - right: 0, - top: 0, - bottom: 0, - child: Opacity( - opacity: 0, - child: Listener( - onPointerDown: (_) { - if (widget.selectByTap) { - double distanceFromLeftHandler, - distanceFromRightHandler, - tappedPositionWithPadding; - - if (widget.axis == Axis.horizontal) { - tappedPositionWithPadding = - _handlersPadding + (_touchSize) - xDragTmp; - distanceFromLeftHandler = ((_leftHandlerXPosition + - _handlersPadding + - (_touchSize)) + - _containerLeft - - _.position.dx) - .abs(); - distanceFromRightHandler = ((_rightHandlerXPosition + - _handlersPadding + - (_touchSize)) + - _containerLeft - - _.position.dx) - .abs(); - } else { - tappedPositionWithPadding = - _handlersPadding + (_touchSize) - yDragTmp; - distanceFromLeftHandler = ((_leftHandlerYPosition + - _handlersPadding + - (_touchSize)) + - _containerTop - - _.position.dy) - .abs(); - distanceFromRightHandler = ((_rightHandlerYPosition + - _handlersPadding + - (_touchSize)) + - _containerTop - - _.position.dy) - .abs(); - } - - if (distanceFromLeftHandler < distanceFromRightHandler) { - if (!widget.rangeSlider) { - _rightHandlerMove(_, tappedPositionWithPadding); + List items = [] + ..addAll(_points) + ..addAll([ +// Function.apply(_leftInactiveTrack, []), +// Function.apply(_rightInactiveTrack, []), + Function.apply(_inactiveTrack, []), + Function.apply(_activeTrack, []), + Positioned( + left: 0, + right: 0, + top: 0, + bottom: 0, + child: Opacity( + opacity: 0, + child: Listener( + onPointerDown: (_) { + if (widget.selectByTap) { + double distanceFromLeftHandler, + distanceFromRightHandler, + tappedPositionWithPadding; + + if (widget.axis == Axis.horizontal) { + tappedPositionWithPadding = + _handlersPadding + (_touchSize) - xDragTmp; + distanceFromLeftHandler = ((_leftHandlerXPosition + + _handlersPadding + + (_touchSize)) + + _containerLeft - + _.position.dx) + .abs(); + distanceFromRightHandler = ((_rightHandlerXPosition + + _handlersPadding + + (_touchSize)) + + _containerLeft - + _.position.dx) + .abs(); } else { - _leftHandlerMove(_, tappedPositionWithPadding); + tappedPositionWithPadding = + _handlersPadding + (_touchSize) - yDragTmp; + distanceFromLeftHandler = ((_leftHandlerYPosition + + _handlersPadding + + (_touchSize)) + + _containerTop - + _.position.dy) + .abs(); + distanceFromRightHandler = ((_rightHandlerYPosition + + _handlersPadding + + (_touchSize)) + + _containerTop - + _.position.dy) + .abs(); } - } else - _rightHandlerMove(_, tappedPositionWithPadding); - } - }, - child: Container( + + if (distanceFromLeftHandler < distanceFromRightHandler) { + if (!widget.rangeSlider) { + _rightHandlerMove(_, tappedPositionWithPadding); + } else { + _leftHandlerMove(_, tappedPositionWithPadding); + } + } else + _rightHandlerMove(_, tappedPositionWithPadding); + } + }, + child: Container( + color: Colors.redAccent, + ), ), - ), - )), - ]; + )), + ]); for (Function func in _positionedItems) { items.add(Function.apply(func, [])); @@ -1081,6 +1208,17 @@ class _FlutterSliderState extends State ), )); +// double top, left, right, bottom; +// top = left = right = bottom = -50; +// if(widget.axis == Axis.horizontal) { +// top = -(_containerHeight - _handlersHeight); +// bottom = null; +// } else { +// top = bottom = 20; +// right = null; +// left = -(_containerWidth - _handlersWidth); +// } + double top = -(_containerHeight - _handlersHeight); if (widget.axis == Axis.vertical) top = -_handlersHeight + 10; @@ -1152,11 +1290,11 @@ class _FlutterSliderState extends State width = _rightHandlerXPosition - _leftHandlerXPosition; left = _leftHandlerXPosition + _handlersWidth / 2 + (_touchSize); if (widget.rtl == true && widget.rangeSlider == false) { - left = _rightHandlerXPosition + (_touchSize); - width = _constraintMaxWidth - + left = null; + right = _handlersPadding; + width = _containerWidthWithoutPadding - _rightHandlerXPosition - - _handlersWidth / 2 - - (_touchSize); + _handlersPadding / 2; } } else { right = 0; @@ -1164,11 +1302,11 @@ class _FlutterSliderState extends State height = _rightHandlerYPosition - _leftHandlerYPosition; top = _leftHandlerYPosition + _handlersHeight / 2 + (_touchSize); if (widget.rtl == true && widget.rangeSlider == false) { - top = _rightHandlerYPosition + (_touchSize); - height = _constraintMaxHeight - + top = null; + bottom = _handlersPadding; + height = _containerHeightWithoutPadding - _rightHandlerYPosition - - _handlersHeight / 2 - - (_touchSize); + _handlersPadding / 2; } } @@ -1216,6 +1354,10 @@ class _FlutterSliderState extends State double _displayRealValue(double value) { return double.parse((value + widget.min).toStringAsFixed(_decimalScale)); + // return (value + widget.min); +// if(_decimalScale > 0) { +// } +// return double.parse((value + widget.min).floor().toStringAsFixed(_decimalScale)); } void _arrangeHandlersZIndex() { @@ -1424,3 +1566,60 @@ class FlutterSliderHandlerAnimation { this.scale = 1.3}) : assert(curve != null && duration != null && scale != null); } + +class FlutterSliderHatchMark { + bool disabled; + double density; + double distanceFromTrackBar; + TextStyle labelTextStyle; + List labels; + FlutterSliderSizedBox smallLine; + FlutterSliderSizedBox bigLine; + FlutterSliderSizedBox labelBox; + + FlutterSliderHatchMark({ + this.disabled = false, + this.density = 1, + this.distanceFromTrackBar, + this.labels, + this.labelTextStyle, + this.smallLine, + this.bigLine, + this.labelBox, + }) : assert(disabled != null), + assert(density != null && density > 0 && density <= 2); +} + +class FlutterSliderHatchMarkLabel { + final double percent; + final String label; + + FlutterSliderHatchMarkLabel({this.percent, this.label}) + : assert((label == null && percent == null) || + (label != null && percent != null && percent >= 0)); +} + +class FlutterSliderSizedBox { + final BoxDecoration decoration; + final BoxDecoration foregroundDecoration; + final Matrix4 transform; + final double width; + final double height; + + const FlutterSliderSizedBox( + {this.decoration, + this.foregroundDecoration, + this.transform, + @required this.height, + @required this.width}) + : assert(width != null && height != null && width > 0 && height > 0); + + @override + String toString() { + return width.toString() + + height.toString() + + decoration.toString() + + foregroundDecoration.toString() + + transform.toString(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 499de8f..63e982e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_xlider description: (Flutter Xlider) A material design slider and range slider, horizontal and vertical, with RTL support and lots of options and customizations for flutter -version: 2.3.1 +version: 2.4.0 author: Ali Azmoude homepage: https://github.com/Ali-Azmoud/flutter_xlider