Skip to content

Commit

Permalink
feat: add interactivity with stream
Browse files Browse the repository at this point in the history
  • Loading branch information
davidanaya committed May 4, 2019
1 parent 73d564a commit fa28203
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [1.1.0] - 2019-05-04

- Add new feature to be able to start/stop the wheel from the outside.

## [1.0.1] - 2019-04-28

- Fix issue in pubspec.yaml
Expand Down
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,23 @@ You can replace the image with one of your preference.

### Constructor

| Parameter | Default | Description |
| :----------------------- | :----------------------: | :------------------------------------------------------------------------------------------------ |
| image | | The image to be used as wheel. |
| dividers | | The number of divisions in the image. It's important that all divisions are equal. |
| height | | Height of the container that will display the wheel. |
| width | | Width of the container that will display the wheel. |
| initialSpinAngle | 0.0 | Initial rotation angle for the wheel, so the wheel could look initialy rotated. |
| spinResistance | 0.5 | From >0.0 to 1.0 will be used to calculate the speed and deceleration of the wheel. |
| canInteractWhileSpinning | true | If set to false, once the animation starts the user won't be able to stop it. |
| secondaryImage | | Secondary image that will be rendered on top of the wheel and won't be affected by the animation. |
| secondaryImageHeight | | Height for the secondary image. |
| secondaryImageWidth | | Width for the secondary image. |
| secondaryImageTop | | Used to fine tune the position of the secondary image. Otherwise it will be centered. |
| secondaryImageLeft | | Used to fine tune the position of the secondary image. Otherwise it will be centered. |
| onUpdate | void onUpdate(int value) | Callback function executed when the selected divider changes during the animation. |
| onEnd | void onEnd(int value) | Callback function executed when the animation stops. |
| Parameter | Default | Description |
| :----------------------- | :----------------------: | :---------------------------------------------------------------------------------------------------- |
| image | | The image to be used as wheel. |
| dividers | | The number of divisions in the image. It's important that all divisions are equal. |
| height | | Height of the container that will display the wheel. |
| width | | Width of the container that will display the wheel. |
| initialSpinAngle | 0.0 | Initial rotation angle for the wheel, so the wheel could look initialy rotated. |
| spinResistance | 0.5 | From >0.0 to 1.0 will be used to calculate the speed and deceleration of the wheel. |
| canInteractWhileSpinning | true | If set to false, once the animation starts the user won't be able to stop it. |
| secondaryImage | | Secondary image that will be rendered on top of the wheel and won't be affected by the animation. |
| secondaryImageHeight | | Height for the secondary image. |
| secondaryImageWidth | | Width for the secondary image. |
| secondaryImageTop | | Used to fine tune the position of the secondary image. Otherwise it will be centered. |
| secondaryImageLeft | | Used to fine tune the position of the secondary image. Otherwise it will be centered. |
| onUpdate | void onUpdate(int value) | Callback function executed when the selected divider changes during the animation. |
| onEnd | void onEnd(int value) | Callback function executed when the animation stops. |
| shouldStartOrStop | | Stream<double> to interact with the wheel, with double being pixelsPerSecond in axis y (default 8000) |

### Use Cases

Expand Down
12 changes: 12 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@ class BasicScore extends StatelessWidget {
class Roulette extends StatelessWidget {
final StreamController _dividerController = StreamController<int>();

final _wheelNotifier = StreamController<double>();

dispose() {
_dividerController.close();
_wheelNotifier.close();
}

@override
Expand All @@ -167,19 +170,28 @@ class Roulette extends StatelessWidget {
Image.asset('assets/images/roulette-center-300.png'),
secondaryImageHeight: 110,
secondaryImageWidth: 110,
shouldStartOrStop: _wheelNotifier.stream,
),
SizedBox(height: 30),
StreamBuilder(
stream: _dividerController.stream,
builder: (context, snapshot) =>
snapshot.hasData ? RouletteScore(snapshot.data) : Container(),
),
SizedBox(height: 30),
new RaisedButton(
child: new Text("Start"),
onPressed: () =>
_wheelNotifier.sink.add(_generateRandomVelocity()),
)
],
),
),
);
}

double _generateRandomVelocity() => (Random().nextDouble() * 6000) + 2000;

double _generateRandomAngle() => Random().nextDouble() * pi * 2;
}

Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.0.1"
version: "1.1.0"
flutter_test:
dependency: "direct dev"
description: flutter
Expand Down
52 changes: 42 additions & 10 deletions lib/src/spinning_wheel.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
Expand Down Expand Up @@ -57,6 +58,11 @@ class SpinningWheel extends StatefulWidget {
/// callback function to be executed when the animation stops
final Function onEnd;

/// Stream<double> used to trigger an animation
/// if triggered in an animation it will stop it, unless canInteractWhileSpinning is false
/// the parameter is a double for pixelsPerSecond in axis Y, which defaults to 8000.0 as a medium-high velocity
final Stream shouldStartOrStop;

SpinningWheel(
this.image, {
@required this.width,
Expand All @@ -72,6 +78,7 @@ class SpinningWheel extends StatefulWidget {
this.secondaryImageLeft,
this.onUpdate,
this.onEnd,
this.shouldStartOrStop,
}) : assert(width > 0.0 && height > 0.0),
assert(spinResistance > 0.0 && spinResistance <= 1.0),
assert(initialSpinAngle >= 0.0 && initialSpinAngle <= (2 * pi)),
Expand All @@ -88,7 +95,7 @@ class _SpinningWheelState extends State<SpinningWheel>
Animation<double> _animation;

// we need to store if has the widget behaves differently depending on the status
AnimationStatus _animationStatus = AnimationStatus.dismissed;
// AnimationStatus _animationStatus = AnimationStatus.dismissed;

// it helps calculating the velocity based on position and pixels per second velocity and angle
SpinVelocity _spinVelocity;
Expand Down Expand Up @@ -125,6 +132,9 @@ class _SpinningWheelState extends State<SpinningWheel>
// will be used to do transformations between global and local
RenderBox _renderBox;

// subscription to the stream used to trigger an animation
StreamSubscription _subscription;

@override
void initState() {
super.initState();
Expand All @@ -143,9 +153,25 @@ class _SpinningWheelState extends State<SpinningWheel>
_initialSpinAngle = widget.initialSpinAngle;

_animation.addStatusListener((status) {
_animationStatus = status;
// _animationStatus = status;
if (status == AnimationStatus.completed) _stopAnimation();
});

if (widget.shouldStartOrStop != null) {
_subscription = widget.shouldStartOrStop.listen(_startOrStop);
}
}

_startOrStop(dynamic velocity) {
if (_animationController.isAnimating) {
_stopAnimation();
} else {
// velocity is pixels per second in axis Y
// we asume a drag from cuadrant 1 with high velocity (8000)
var pixelsPerSecondY = velocity ?? 8000.0;
_localPositionOnPanUpdate = Offset(250.0, 250.0);
_startAnimation(Offset(0.0, pixelsPerSecondY));
}
}

double get topSecondaryImage =>
Expand All @@ -170,7 +196,7 @@ class _SpinningWheelState extends State<SpinningWheel>
children: [
GestureDetector(
onPanUpdate: _moveWheel,
onPanEnd: _startAnimation,
onPanEnd: _startAnimationOnPanEnd,
onPanDown: (_details) => _stopAnimation(),
child: AnimatedBuilder(
animation: _animation,
Expand Down Expand Up @@ -201,8 +227,7 @@ class _SpinningWheelState extends State<SpinningWheel>

// user can interact only if widget allows or wheel is not spinning
bool get _userCanInteract =>
_animationStatus != AnimationStatus.forward ||
widget.canInteractWhileSpinning;
!_animationController.isAnimating || widget.canInteractWhileSpinning;

// transforms from global coordinates to local and store the value
void _updateLocalPosition(Offset position) {
Expand All @@ -217,7 +242,7 @@ class _SpinningWheelState extends State<SpinningWheel>

// this is called just before the animation starts
void _updateAnimationValues() {
if (_animationStatus == AnimationStatus.forward) {
if (_animationController.isAnimating) {
// calculate total distance covered
var currentTime = _totalDuration * _animation.value;
_currentDistance =
Expand All @@ -229,7 +254,7 @@ class _SpinningWheelState extends State<SpinningWheel>
// calculate current divider selected
var modulo = _motion.modulo(_currentDistance + _initialSpinAngle);
_currentDivider = widget.dividers - (modulo ~/ _dividerAngle);
if (_animationStatus != AnimationStatus.forward) {
if (_animationController.isCompleted) {
_initialSpinAngle = modulo;
_currentDistance = 0;
}
Expand Down Expand Up @@ -268,7 +293,7 @@ class _SpinningWheelState extends State<SpinningWheel>
widget.onEnd(_currentDivider);
}

void _startAnimation(DragEndDetails details) {
void _startAnimationOnPanEnd(DragEndDetails details) {
if (!_userCanInteract) return;

if (_offsetOutsideTimestamp != null) {
Expand All @@ -281,8 +306,12 @@ class _SpinningWheelState extends State<SpinningWheel>
// it was the user just taping to stop the animation
if (_localPositionOnPanUpdate == null) return;

var velocity = _spinVelocity.getVelocity(
_localPositionOnPanUpdate, details.velocity.pixelsPerSecond);
_startAnimation(details.velocity.pixelsPerSecond);
}

void _startAnimation(Offset pixelsPerSecond) {
var velocity =
_spinVelocity.getVelocity(_localPositionOnPanUpdate, pixelsPerSecond);

_localPositionOnPanUpdate = null;
_isBackwards = velocity < 0;
Expand All @@ -298,6 +327,9 @@ class _SpinningWheelState extends State<SpinningWheel>

dispose() {
_animationController.dispose();
if (_subscription != null) {
_subscription.cancel();
}
super.dispose();
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_spinning_wheel
description: A customizable widget to use as a spinning wheel in Flutter.
version: 1.0.1
version: 1.1.0
author: David Anaya <[email protected]>
homepage: https://github.com/davidanaya/flutter-spinning-wheel
repository: https://github.com/davidanaya/flutter-spinning-wheel
Expand Down

0 comments on commit fa28203

Please sign in to comment.