From 33181aa547f7f42fe64bdf536abc4917ebd65ebd Mon Sep 17 00:00:00 2001 From: Patrick Chrestin Date: Sat, 8 Jun 2024 15:38:18 +0200 Subject: [PATCH] Added parameter to allow for an indefinite pager indicator behavior --- CHANGELOG.md | 6 ++ analysis_options.yaml | 1 + example/android/app/build.gradle | 2 +- example/android/build.gradle | 2 +- example/lib/main.dart | 15 ++++- example/pubspec.yaml | 2 +- lib/src/dots_decorator.dart | 18 ++++++ lib/src/dots_indicator.dart | 97 +++++++++++++++++++++++++------- pubspec.yaml | 4 +- 9 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 analysis_options.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b81a2..ad9410d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## NEXT +* Decimal places of the current position removed in the example +* Updated compatible dart sdk version +* Added animation parameters to animate changes in dot styling +* Added parameter to allow for an indefinite pager indicator behavior + ## 3.0.0 * Changed position to int + fixed sample (Thanks to [PR#24](https://github.com/Pyozer/dots_indicator/pull/24)) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index b2125a3..16cd018 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -35,7 +35,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.dots_indicator_example" - minSdkVersion 16 + minSdkVersion flutter.minSdkVersion targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/build.gradle b/example/android/build.gradle index a4d7066..0ce0993 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/lib/main.dart b/example/lib/main.dart index 4179ea4..1d5ed0f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,7 @@ import 'dart:math'; -import 'package:flutter/material.dart'; import 'package:dots_indicator/dots_indicator.dart'; +import 'package:flutter/material.dart'; void main() => runApp(MyApp()); @@ -35,7 +35,7 @@ class _MyAppState extends State { } String getPrettyCurrPosition() { - return (_currentPosition + 1.0).toStringAsPrecision(3); + return (_currentPosition + 1).toString(); } @override @@ -202,6 +202,17 @@ class _MyAppState extends State { decorator: decorator, ), ]), + _buildRow([ + const Text('Indefinite Pager Indicator'), + DotsIndicator( + dotsCount: _totalDots, + position: _currentPosition, + decorator: decorator, + fadeOutLastDot: true, + fadeOutDistance: 2, + animate: true, + ), + ]), ], ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c8f6aa8..a96d594 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0 publish_to: 'none' environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" dependencies: flutter: diff --git a/lib/src/dots_decorator.dart b/lib/src/dots_decorator.dart index c08a23c..dc4f3ef 100644 --- a/lib/src/dots_decorator.dart +++ b/lib/src/dots_decorator.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; const Size kDefaultSize = Size.square(9.0); +const Size kDefaultFadeOutSize = Size.square(6.0); const EdgeInsets kDefaultSpacing = EdgeInsets.all(6.0); const ShapeBorder kDefaultShape = CircleBorder(); @@ -38,6 +39,17 @@ class DotsDecorator { /// @Default `Value of size parameter applied to each dot` final List sizes; + /// Fade out dot size + /// + /// @Default `Size.square(6.0)` + final Size fadeOutSize; + + /// List of fade out dot size + /// One size by dot + /// + /// @Default `Value of activeSize parameter applied to each dot` + final List fadeOutSizes; + /// Active dot size /// /// @Default `Size.square(9.0)` @@ -88,6 +100,8 @@ class DotsDecorator { this.activeColors = const [], this.size = kDefaultSize, this.sizes = const [], + this.fadeOutSize = kDefaultFadeOutSize, + this.fadeOutSizes = const [], this.activeSize = kDefaultSize, this.activeSizes = const [], this.shape = kDefaultShape, @@ -106,6 +120,10 @@ class DotsDecorator { return colors.isNotEmpty ? colors[index] : color; } + Size getFadeOutSize(int index) { + return fadeOutSizes.isNotEmpty ? fadeOutSizes[index] : fadeOutSize; + } + Size getActiveSize(int index) { return activeSizes.isNotEmpty ? activeSizes[index] : activeSize; } diff --git a/lib/src/dots_indicator.dart b/lib/src/dots_indicator.dart index 5d55319..a2d1fbd 100644 --- a/lib/src/dots_indicator.dart +++ b/lib/src/dots_indicator.dart @@ -5,10 +5,16 @@ import 'dart:math'; import 'package:dots_indicator/src/dots_decorator.dart'; import 'package:flutter/material.dart'; -typedef void OnTap(int position); +typedef OnTap = void Function(int position); class DotsIndicator extends StatelessWidget { final int dotsCount, position; + + /// If true, the last dot will fade out when the position is at the last dot. + final bool fadeOutLastDot; + + /// Distance from currently selected dot to the one that fades out. + final int fadeOutDistance; final DotsDecorator decorator; final Axis axis; final bool reversed; @@ -16,8 +22,14 @@ class DotsIndicator extends StatelessWidget { final MainAxisSize mainAxisSize; final MainAxisAlignment mainAxisAlignment; + /// If true, the dots will animate when the position changes. + final bool animate; + + /// Duration of the animation when the position changes. + final Duration animationDuration; + DotsIndicator({ - Key? key, + super.key, required this.dotsCount, this.position = 0, this.decorator = const DotsDecorator(), @@ -26,6 +38,10 @@ class DotsIndicator extends StatelessWidget { this.mainAxisSize = MainAxisSize.min, this.mainAxisAlignment = MainAxisAlignment.center, this.onTap, + this.fadeOutLastDot = false, + this.fadeOutDistance = 0, + this.animate = false, + this.animationDuration = const Duration(milliseconds: 200), }) : assert(dotsCount > 0, 'dotsCount must be superior to zero'), assert(position >= 0, 'position must be superior or equals to zero'), assert( @@ -59,7 +75,14 @@ class DotsIndicator extends StatelessWidget { decorator.activeShapes.length == dotsCount, "activeShapes param in decorator must empty or have same length as dotsCount parameter", ), - super(key: key); + assert( + fadeOutLastDot == false || fadeOutDistance > 0, + "fadeOutDistace must be superior to zero when fadeOutLastDot is true", + ), + assert( + fadeOutDistance < dotsCount, + "fadeOutDistace must be inferior to dotsCount", + ); Widget _wrapInkwell(Widget dot, int index) { return InkWell( @@ -72,30 +95,66 @@ class DotsIndicator extends StatelessWidget { } Widget _buildDot(BuildContext context, int index) { - final double lerpValue = min(1, (position - index).abs()).toDouble(); + final int absPositionIndexRelation = (position - index).abs(); + final bool isCurrentlyVisible = absPositionIndexRelation <= fadeOutDistance; - final size = Size.lerp( + final double lerpValue = min(1, absPositionIndexRelation).toDouble(); + + Size size = Size.lerp( decorator.getActiveSize(index), decorator.getSize(index), lerpValue, )!; + if (fadeOutLastDot && absPositionIndexRelation >= fadeOutDistance) { + size = Size.lerp( + decorator.getSize(index), + decorator.getFadeOutSize(index), + absPositionIndexRelation == fadeOutDistance ? 1 : 0.0, + )!; + } + final dot = Container( - width: size.width, - height: size.height, - margin: decorator.spacing, - decoration: ShapeDecoration( - color: Color.lerp( - decorator.getActiveColor(index) ?? Theme.of(context).primaryColor, - decorator.getColor(index), - lerpValue, + height: fadeOutLastDot && isCurrentlyVisible + ? max( + max(decorator.getActiveSize(index).height, + decorator.getSize(index).height), + decorator.getFadeOutSize(index).height, + ) + + (axis == Axis.horizontal + ? decorator.spacing.vertical + : decorator.spacing.horizontal) + : null, + child: Center( + child: AnimatedOpacity( + duration: animate ? animationDuration : Duration.zero, + opacity: + !fadeOutLastDot || absPositionIndexRelation <= fadeOutDistance + ? 1.0 + : 0.0, + child: AnimatedContainer( + duration: animate ? animationDuration : Duration.zero, + width: size.width, + height: size.height, + margin: fadeOutLastDot && !isCurrentlyVisible + ? EdgeInsets.all(0) + : decorator.spacing, + decoration: ShapeDecoration( + color: Color.lerp( + decorator.getActiveColor(index) ?? + Theme.of(context).primaryColor, + decorator.getColor(index), + lerpValue, + ), + shape: ShapeBorder.lerp( + decorator.getActiveShape(index), + decorator.getShape(index), + lerpValue, + )!, + shadows: decorator.shadows, + ), + ), ), - shape: ShapeBorder.lerp( - decorator.getActiveShape(index), - decorator.getShape(index), - lerpValue, - )!, - shadows: decorator.shadows, ), ); return onTap == null ? dot : _wrapInkwell(dot, index); diff --git a/pubspec.yaml b/pubspec.yaml index 0b10c90..fb07ea5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,8 +4,10 @@ version: 3.0.0 homepage: https://github.com/pyozer/dots_indicator environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.12.0 <4.0.0' dependencies: flutter: sdk: flutter +dev_dependencies: + lints: ^4.0.0