From 0062f7b27fbc96fdc7945050a8aaefdeca8c5009 Mon Sep 17 00:00:00 2001 From: EricPKerr Date: Tue, 14 Jan 2020 11:50:49 -0600 Subject: [PATCH 1/4] Initial refactor AnimatedPoint into AnimatedCoordinates with LineString --- javascript/utils/AnimatedCoordinates.js | 119 ++++++++++++++++++++++++ javascript/utils/AnimatedLineString.js | 108 +++++++++++++++++++++ javascript/utils/AnimatedPoint.js | 85 ++++------------- 3 files changed, 245 insertions(+), 67 deletions(-) create mode 100644 javascript/utils/AnimatedCoordinates.js create mode 100644 javascript/utils/AnimatedLineString.js diff --git a/javascript/utils/AnimatedCoordinates.js b/javascript/utils/AnimatedCoordinates.js new file mode 100644 index 000000000..8d53121bb --- /dev/null +++ b/javascript/utils/AnimatedCoordinates.js @@ -0,0 +1,119 @@ +import { Animated } from 'react-native'; + +// Used react-native-maps as a reference +// https://github.com/react-community/react-native-maps/blob/master/lib/components/AnimatedRegion.js +const AnimatedWithChildren = Object.getPrototypeOf(Animated.ValueXY); + +const DEFAULT_COORD = [0, 0]; + +let uniqueID = 0; + +export class AnimatedCoordinates extends AnimatedWithChildren { + constructor(coordinates = DEFAULT_POINT) { + super(); + + this.longitude = coordinates[0] || 0; + this.latitude = coordinates[1] || 0; + + if (!(this.longitude instanceof Animated.Value)) { + this.longitude = new Animated.Value(this.longitude); + } + + if (!(this.latitude instanceof Animated.Value)) { + this.latitude = new Animated.Value(this.latitude); + } + + this._listeners = {}; + } + + setValue(coordinates = DEFAULT_COORD) { + this.longitude.setValue(coordinates[0]); + this.latitude.setValue(coordinates[1]); + } + + setOffset(coordinates = DEFAULT_COORD) { + this.longitude.setOffset(coordinates[0]); + this.latitude.setOffset(coordinates[1]); + } + + flattenOffset() { + this.longitude.flattenOffset(); + this.latitude.flattenOffset(); + } + + stopAnimation(cb) { + this.longitude.stopAnimation(); + this.latitude.stopAnimation(); + + if (typeof cb === 'function') { + cb(this.__getValue()); + } + } + + addListener(cb) { + uniqueID += 1; + const id = `${String(uniqueID)}-${String(Date.now())}`; + + const completeCB = () => { + if (typeof cb === 'function') { + cb(this.__getValue()); + } + }; + + this._listeners[id] = { + longitude: this.longitude.addListener(completeCB), + latitude: this.latitude.addListener(completeCB) + }; + + return id; + } + + removeListener(id) { + this.longitude.removeListener(this._listeners[id].longitude); + this.latitude.removeListener(this._listeners[id].latitude); + delete this._listeners[id]; + } + + spring(config = { coordinates: DEFAULT_COORD }) { + return Animated.parallel([ + Animated.spring(this.longitude, { + ...config, + toValue: config.coordinates[0] + }), + Animated.spring(this.latitude, { + ...config, + toValue: config.coordinates[1] + }) + ]); + } + + timing(config = { coordinates: DEFAULT_COORD }) { + return Animated.parallel([ + Animated.timing(this.longitude, { + ...config, + toValue: config.coordinates[0] + }), + Animated.timing(this.latitude, { + ...config, + toValue: config.coordinates[1] + }) + ]); + } + + __getValue() { + return [this.longitude.__getValue(), this.latitude.__getValue()]; + } + + __attach(self) { + console.log('__attach', self); + this.longitude.__addChild(self || this); + this.latitude.__addChild(self || this); + } + + __detach(self) { + this.longitude.__removeChild(self || this); + this.latitude.__removeChild(self || this); + } +} + +export default AnimatedCoordinates; diff --git a/javascript/utils/AnimatedLineString.js b/javascript/utils/AnimatedLineString.js new file mode 100644 index 000000000..d59513a71 --- /dev/null +++ b/javascript/utils/AnimatedLineString.js @@ -0,0 +1,108 @@ +import { Animated } from 'react-native'; + +// Used react-native-maps as a reference +// https://github.com/react-community/react-native-maps/blob/master/lib/components/AnimatedRegion.js +const AnimatedWithChildren = Object.getPrototypeOf(Animated.ValueXY); + +import AnimatedCoordinates from './AnimatedCoordinates'; + +const DEFAULT_COORD = [0, 0]; + +const DEFAULT_LINE = { + type: 'LineString', + coordinates: [DEFAULT_COORD, DEFAULT_COORD] +}; + +export class AnimatedLineString extends AnimatedWithChildren { + constructor(line = DEFAULT_LINE) { + super(); + + let coordinates = []; + + line.coordinates.forEach((coord) => { + coordinates.push(new AnimatedCoordinates(coord)); + }); + + this.coordinates = coordinates; + + this._listeners = {}; + } + + setValue(line = DEFAULT_LINE) { + // TODO + this.coordinates.setValue(point.coordinates); + } + + setOffset(line = DEFAULT_LINE) { + // TODO + this.coordinates.setOffset(point.coordinates); + } + + flattenOffset() { + this.coordinates.forEach((coord) => { + coord.flattenOffset(); + }); + } + + stopAnimation(cb) { + this.coordinates.forEach((coord) => { + coord.stopAnimation(); + }); + + if (typeof cb === 'function') { + cb(this.__getValue()); + } + } + + addListener(cb) { + this.coordinates.forEach((coord) => { + coord.addListener(cb); + }); + } + + removeListener(id) { + this.coordinates.forEach((coord) => { + coord.removeListener(id); + }); + } + + spring(config = { coordinates: DEFAULT_COORD }) { + // TODO + return Animated.parallel( + this.coordinates.map((coord) => { + return coord.spring(config); + }) + ); + } + + timing(config = { coordinates: DEFAULT_COORD }) { + // TODO + return Animated.parallel( + this.coordinates.map((coord, i) => { + return coord.timing({ + ...config, + coordinates: config.coordinates[i] + }); + }) + ); + } + + __getValue() { + return { + type: 'LineString', + coordinates: this.coordinates.map((coord) => coord.__getValue()) + }; + } + + __attach() { + const self = this; + this.coordinates.forEach((coord) => coord.__attach(self)); + } + + __detach() { + const self = this; + this.coordinates.forEach((coord) => coord.__detach(self)); + } +} + +export default AnimatedLineString; diff --git a/javascript/utils/AnimatedPoint.js b/javascript/utils/AnimatedPoint.js index 244eac80e..eeba5662b 100644 --- a/javascript/utils/AnimatedPoint.js +++ b/javascript/utils/AnimatedPoint.js @@ -1,50 +1,37 @@ -import {Animated} from 'react-native'; +import { Animated } from 'react-native'; // Used react-native-maps as a reference // https://github.com/react-community/react-native-maps/blob/master/lib/components/AnimatedRegion.js const AnimatedWithChildren = Object.getPrototypeOf(Animated.ValueXY); -const DEFAULT_COORD = [0, 0]; -const DEFAULT_POINT = {type: 'Point', coordinates: DEFAULT_COORD}; +import AnimatedCoordinates from './AnimatedCoordinates'; -let uniqueID = 0; +const DEFAULT_COORD = [0, 0]; +const DEFAULT_POINT = { type: 'Point', coordinates: DEFAULT_COORD }; export class AnimatedPoint extends AnimatedWithChildren { constructor(point = DEFAULT_POINT) { super(); - this.longitude = point.coordinates[0] || 0; - this.latitude = point.coordinates[1] || 0; - - if (!(this.longitude instanceof Animated.Value)) { - this.longitude = new Animated.Value(this.longitude); - } - - if (!(this.latitude instanceof Animated.Value)) { - this.latitude = new Animated.Value(this.latitude); - } + this.coordinates = new AnimatedCoordinates(point.coordinates); this._listeners = {}; } setValue(point = DEFAULT_POINT) { - this.longitude.setValue(point.coordinates[0]); - this.latitude.setValue(point.coordinates[1]); + this.coordinates.setValue(point.coordinates); } setOffset(point = DEFAULT_POINT) { - this.longitude.setOffset(point.coordinates[0]); - this.latitude.setOffset(point.coordinates[1]); + this.coordinates.setOffset(point.coordinates); } flattenOffset() { - this.longitude.flattenOffset(); - this.latitude.flattenOffset(); + this.coordinates.flattenOffset(); } stopAnimation(cb) { - this.longitude.stopAnimation(); - this.latitude.stopAnimation(); + this.coordinates.stopAnimation(); if (typeof cb === 'function') { cb(this.__getValue()); @@ -52,70 +39,34 @@ export class AnimatedPoint extends AnimatedWithChildren { } addListener(cb) { - uniqueID += 1; - const id = `${String(uniqueID)}-${String(Date.now())}`; - - const completeCB = () => { - if (typeof cb === 'function') { - cb(this.__getValue()); - } - }; - - this._listeners[id] = { - longitude: this.longitude.addListener(completeCB), - latitude: this.latitude.addListener(completeCB), - }; - - return id; + return this.coordinates.addListener(cb); } removeListener(id) { - this.longitude.removeListener(this._listeners[id].longitude); - this.latitude.removeListener(this._listeners[id].latitude); - delete this._listeners[id]; + return this.coordinates.removeListener(id); } - spring(config = {coordinates: DEFAULT_COORD}) { - return Animated.parallel([ - Animated.spring(this.longitude, { - ...config, - toValue: config.coordinates[0], - }), - Animated.spring(this.latitude, { - ...config, - toValue: config.coordinates[1], - }), - ]); + spring(config = { coordinates: DEFAULT_COORD }) { + return this.coordinates.spring(config); } - timing(config = {coordinates: DEFAULT_COORD}) { - return Animated.parallel([ - Animated.timing(this.longitude, { - ...config, - toValue: config.coordinates[0], - }), - Animated.timing(this.latitude, { - ...config, - toValue: config.coordinates[1], - }), - ]); + timing(config = { coordinates: DEFAULT_COORD }) { + return this.coordinates.timing(config); } __getValue() { return { type: 'Point', - coordinates: [this.longitude.__getValue(), this.latitude.__getValue()], + coordinates: this.coordinates.__getValue() }; } __attach() { - this.longitude.__addChild(this); - this.latitude.__addChild(this); + this.coordinates.__attach(this); } __detach() { - this.longitude.__removeChild(this); - this.latitude.__removeChild(this); + this.coordinates.__detach(this); } } From 4f6c8f451fbe83be6c6478c13ee55cadd33c02e7 Mon Sep 17 00:00:00 2001 From: EricPKerr Date: Thu, 16 Jan 2020 17:11:37 -0600 Subject: [PATCH 2/4] Update Animated classes --- javascript/components/annotations/Annotation.js | 2 +- javascript/index.js | 2 +- javascript/utils/{ => animated}/AnimatedCoordinates.js | 0 javascript/utils/{ => animated}/AnimatedLineString.js | 0 javascript/utils/{ => animated}/AnimatedPoint.js | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename javascript/utils/{ => animated}/AnimatedCoordinates.js (100%) rename javascript/utils/{ => animated}/AnimatedLineString.js (100%) rename javascript/utils/{ => animated}/AnimatedPoint.js (100%) diff --git a/javascript/components/annotations/Annotation.js b/javascript/components/annotations/Annotation.js index 457923344..d2f8b531b 100644 --- a/javascript/components/annotations/Annotation.js +++ b/javascript/components/annotations/Annotation.js @@ -3,7 +3,7 @@ import {Easing} from 'react-native'; import PropTypes from 'prop-types'; import MapboxGL from '../../index'; // eslint-disable-line import/no-cycle -import AnimatedMapPoint from '../../utils/AnimatedPoint'; +import AnimatedMapPoint from '../../utils/animated/AnimatedPoint'; class Annotation extends React.Component { static propTypes = { diff --git a/javascript/index.js b/javascript/index.js index 15bc73061..d696ff261 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -25,7 +25,7 @@ import BackgroundLayer from './components/BackgroundLayer'; import locationManager from './modules/location/locationManager'; import offlineManager from './modules/offline/offlineManager'; import snapshotManager from './modules/snapshot/snapshotManager'; -import AnimatedMapPoint from './utils/AnimatedPoint'; +import AnimatedMapPoint from './utils/animated/AnimatedPoint'; const MapboxGL = {...NativeModules.MGLModule}; diff --git a/javascript/utils/AnimatedCoordinates.js b/javascript/utils/animated/AnimatedCoordinates.js similarity index 100% rename from javascript/utils/AnimatedCoordinates.js rename to javascript/utils/animated/AnimatedCoordinates.js diff --git a/javascript/utils/AnimatedLineString.js b/javascript/utils/animated/AnimatedLineString.js similarity index 100% rename from javascript/utils/AnimatedLineString.js rename to javascript/utils/animated/AnimatedLineString.js diff --git a/javascript/utils/AnimatedPoint.js b/javascript/utils/animated/AnimatedPoint.js similarity index 100% rename from javascript/utils/AnimatedPoint.js rename to javascript/utils/animated/AnimatedPoint.js From 79f65b1691eb9ce0c1933fa2f8058335f8f7aca9 Mon Sep 17 00:00:00 2001 From: EricPKerr Date: Thu, 16 Jan 2020 17:36:12 -0600 Subject: [PATCH 3/4] Update AnimatedLineString methods --- .../utils/animated/AnimatedCoordinates.js | 38 +++--- .../utils/animated/AnimatedLineString.js | 117 ++++++++++++------ javascript/utils/animated/AnimatedPoint.js | 4 + 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/javascript/utils/animated/AnimatedCoordinates.js b/javascript/utils/animated/AnimatedCoordinates.js index 8d53121bb..5ec12029d 100644 --- a/javascript/utils/animated/AnimatedCoordinates.js +++ b/javascript/utils/animated/AnimatedCoordinates.js @@ -4,16 +4,14 @@ import { Animated } from 'react-native'; // https://github.com/react-community/react-native-maps/blob/master/lib/components/AnimatedRegion.js const AnimatedWithChildren = Object.getPrototypeOf(Animated.ValueXY); -const DEFAULT_COORD = [0, 0]; - let uniqueID = 0; export class AnimatedCoordinates extends AnimatedWithChildren { - constructor(coordinates = DEFAULT_POINT) { + constructor(coordinates) { super(); - this.longitude = coordinates[0] || 0; - this.latitude = coordinates[1] || 0; + this.longitude = coordinates[0]; + this.latitude = coordinates[1]; if (!(this.longitude instanceof Animated.Value)) { this.longitude = new Animated.Value(this.longitude); @@ -26,12 +24,12 @@ export class AnimatedCoordinates extends AnimatedWithChildren { this._listeners = {}; } - setValue(coordinates = DEFAULT_COORD) { + setValue(coordinates) { this.longitude.setValue(coordinates[0]); this.latitude.setValue(coordinates[1]); } - setOffset(coordinates = DEFAULT_COORD) { + setOffset(coordinates) { this.longitude.setOffset(coordinates[0]); this.latitude.setOffset(coordinates[1]); } @@ -74,30 +72,29 @@ export class AnimatedCoordinates extends AnimatedWithChildren { delete this._listeners[id]; } - spring(config = { coordinates: DEFAULT_COORD }) { + animate(type, config) { return Animated.parallel([ - Animated.spring(this.longitude, { + Animated[type](this.longitude, { ...config, toValue: config.coordinates[0] }), - Animated.spring(this.latitude, { + Animated[type](this.latitude, { ...config, toValue: config.coordinates[1] }) ]); } + spring(config = { coordinates: DEFAULT_COORD }) { + return this.animate('spring', config); + } + timing(config = { coordinates: DEFAULT_COORD }) { - return Animated.parallel([ - Animated.timing(this.longitude, { - ...config, - toValue: config.coordinates[0] - }), - Animated.timing(this.latitude, { - ...config, - toValue: config.coordinates[1] - }) - ]); + return this.animate('timing', config); + } + + decay(config = { coordinates: DEFAULT_COORD }) { + return this.animate('decay', config); } __getValue() { @@ -105,7 +102,6 @@ export class AnimatedCoordinates extends AnimatedWithChildren { } __attach(self) { - console.log('__attach', self); this.longitude.__addChild(self || this); this.latitude.__addChild(self || this); } diff --git a/javascript/utils/animated/AnimatedLineString.js b/javascript/utils/animated/AnimatedLineString.js index d59513a71..9075c5e3c 100644 --- a/javascript/utils/animated/AnimatedLineString.js +++ b/javascript/utils/animated/AnimatedLineString.js @@ -17,25 +17,13 @@ export class AnimatedLineString extends AnimatedWithChildren { constructor(line = DEFAULT_LINE) { super(); - let coordinates = []; + //this._listeners = {}; + + this.coordinates = []; line.coordinates.forEach((coord) => { - coordinates.push(new AnimatedCoordinates(coord)); + this.coordinates.push(new AnimatedCoordinates(coord)); }); - - this.coordinates = coordinates; - - this._listeners = {}; - } - - setValue(line = DEFAULT_LINE) { - // TODO - this.coordinates.setValue(point.coordinates); - } - - setOffset(line = DEFAULT_LINE) { - // TODO - this.coordinates.setOffset(point.coordinates); } flattenOffset() { @@ -66,25 +54,86 @@ export class AnimatedLineString extends AnimatedWithChildren { }); } + _last(items) { + return items.length > 0 ? items[items.length - 1] : null; + } + + _setup(coordinates) { + const last = this._last(this.coordinates); + + let i = this.coordinates.length; + + while (i < coordinates.length) { + this.coordinates.push(new AnimatedCoordinates(last.__getValue())); + i++; + } + } + + _cleanup(coordinates) { + while (this.coordinates.length > coordinates.length) { + const popped = this.coordinates.pop(); + } + super.__callListeners(this.__getValue()); + } + + animate(type, config) { + const { coordinates, ...rest } = config; + + const last = this._last(coordinates); + + this._setup(coordinates); + + // Animate existing values + let animations = coordinates.map((coord, i) => { + return this.coordinates[i][type]({ + ...config, + coordinates: coordinates[i] + }); + }); + + // Animate remaining values + let i = coordinates.length; + while (this.coordinates[i]) { + animations.push( + this.coordinates[i][type]({ + ...config, + coordinates: last + }) + ); + i++; + } + + return Animated.parallel(animations); + } + + runImmediate(type, coordinates) { + this._setup(coordinates); + + this.coordinates.forEach((coord, i) => { + coord[type](coordinates[i]); + }); + + this._cleanup(coordinates); + } + + setValue(line) { + this.runImmediate('setValue', line.coordinates); + } + + setOffset(line) { + this.runImmediate('setOffset', line.coordinates); + } + spring(config = { coordinates: DEFAULT_COORD }) { - // TODO - return Animated.parallel( - this.coordinates.map((coord) => { - return coord.spring(config); - }) - ); + return this.animate('spring', config); } timing(config = { coordinates: DEFAULT_COORD }) { - // TODO - return Animated.parallel( - this.coordinates.map((coord, i) => { - return coord.timing({ - ...config, - coordinates: config.coordinates[i] - }); - }) - ); + return this.animate('timing', config); + } + + decay(config = { coordinates: DEFAULT_COORD }) { + return this.animate('decay', config); } __getValue() { @@ -95,13 +144,11 @@ export class AnimatedLineString extends AnimatedWithChildren { } __attach() { - const self = this; - this.coordinates.forEach((coord) => coord.__attach(self)); + this.coordinates.forEach((coord) => coord.__attach(this)); } __detach() { - const self = this; - this.coordinates.forEach((coord) => coord.__detach(self)); + this.coordinates.forEach((coord) => coord.__detach(this)); } } diff --git a/javascript/utils/animated/AnimatedPoint.js b/javascript/utils/animated/AnimatedPoint.js index eeba5662b..d65f56158 100644 --- a/javascript/utils/animated/AnimatedPoint.js +++ b/javascript/utils/animated/AnimatedPoint.js @@ -54,6 +54,10 @@ export class AnimatedPoint extends AnimatedWithChildren { return this.coordinates.timing(config); } + decay(config = { coordinates: DEFAULT_COORD }) { + return this.coordinates.decay(config); + } + __getValue() { return { type: 'Point', From f1c738667b57706dc9efb074e5256d1db27cd2b1 Mon Sep 17 00:00:00 2001 From: EricPKerr Date: Thu, 16 Jan 2020 19:24:54 -0600 Subject: [PATCH 4/4] Add resetAnimation function --- javascript/utils/animated/AnimatedCoordinates.js | 5 +++++ javascript/utils/animated/AnimatedLineString.js | 6 ++++++ javascript/utils/animated/AnimatedPoint.js | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/javascript/utils/animated/AnimatedCoordinates.js b/javascript/utils/animated/AnimatedCoordinates.js index 5ec12029d..dfa7109f4 100644 --- a/javascript/utils/animated/AnimatedCoordinates.js +++ b/javascript/utils/animated/AnimatedCoordinates.js @@ -48,6 +48,11 @@ export class AnimatedCoordinates extends AnimatedWithChildren { } } + resetAnimation() { + this.longitude.resetAnimation(); + this.latitude.resetAnimation(); + } + addListener(cb) { uniqueID += 1; const id = `${String(uniqueID)}-${String(Date.now())}`; diff --git a/javascript/utils/animated/AnimatedLineString.js b/javascript/utils/animated/AnimatedLineString.js index 9075c5e3c..57c9195dc 100644 --- a/javascript/utils/animated/AnimatedLineString.js +++ b/javascript/utils/animated/AnimatedLineString.js @@ -42,6 +42,12 @@ export class AnimatedLineString extends AnimatedWithChildren { } } + resetAnimation() { + this.coordinates.forEach((coord) => { + coord.resetAnimation(); + }) + } + addListener(cb) { this.coordinates.forEach((coord) => { coord.addListener(cb); diff --git a/javascript/utils/animated/AnimatedPoint.js b/javascript/utils/animated/AnimatedPoint.js index d65f56158..833c82c7b 100644 --- a/javascript/utils/animated/AnimatedPoint.js +++ b/javascript/utils/animated/AnimatedPoint.js @@ -38,6 +38,10 @@ export class AnimatedPoint extends AnimatedWithChildren { } } + resetAnimation() { + this.coordinates.resetAnimation(); + } + addListener(cb) { return this.coordinates.addListener(cb); }