/** * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * * Facebook reserves all rights not expressly granted. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @providesModule AnExBobble * @flow */ 'use strict'; var React = require('react-native'); var { Animated, Image, PanResponder, StyleSheet, View, } = React; var NUM_BOBBLES = 5; var RAD_EACH = Math.PI / 2 / (NUM_BOBBLES - 2); var RADIUS = 160; var BOBBLE_SPOTS = [...Array(NUM_BOBBLES)].map((_, i) => { // static positions return i === 0 ? {x: 0, y: 0} : { // first bobble is the selector x: -Math.cos(RAD_EACH * (i - 1)) * RADIUS, y: -Math.sin(RAD_EACH * (i - 1)) * RADIUS, }; }); class AnExBobble extends React.Component { constructor(props: Object) { super(props); this.state = {}; this.state.bobbles = BOBBLE_SPOTS.map((_, i) => { return new Animated.ValueXY(); }); this.state.selectedBobble = null; var bobblePanListener = (e, gestureState) => { // async events => change selection var newSelected = computeNewSelected(gestureState); if (this.state.selectedBobble !== newSelected) { if (this.state.selectedBobble !== null) { var restSpot = BOBBLE_SPOTS[this.state.selectedBobble]; Animated.spring(this.state.bobbles[this.state.selectedBobble], { toValue: restSpot, // return previously selected bobble to rest position }).start(); } if (newSelected !== null && newSelected !== 0) { Animated.spring(this.state.bobbles[newSelected], { toValue: this.state.bobbles[0], // newly selected should track the selector }).start(); } this.state.selectedBobble = newSelected; } }; var releaseBobble = () => { this.state.bobbles.forEach((bobble, i) => { Animated.spring(bobble, { toValue: {x: 0, y: 0} // all bobbles return to zero }).start(); }); }; this.state.bobbleResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, onPanResponderGrant: () => { BOBBLE_SPOTS.forEach((spot, idx) => { Animated.spring(this.state.bobbles[idx], { toValue: spot, // spring each bobble to its spot friction: 3, // less friction => bouncier }).start(); }); }, onPanResponderMove: Animated.event( [ null, {dx: this.state.bobbles[0].x, dy: this.state.bobbles[0].y} ], {listener: bobblePanListener} // async state changes with arbitrary logic ), onPanResponderRelease: releaseBobble, onPanResponderTerminate: releaseBobble, }); } render(): ReactElement { return ( {this.state.bobbles.map((_, i) => { var j = this.state.bobbles.length - i - 1; // reverse so lead on top var handlers = j > 0 ? {} : this.state.bobbleResponder.panHandlers; return ( ); })} ); } } var styles = StyleSheet.create({ circle: { position: 'absolute', height: 60, width: 60, borderRadius: 30, borderWidth: 0.5, }, bobbleContainer: { top: -68, paddingRight: 66, flexDirection: 'row', flex: 1, justifyContent: 'flex-end', backgroundColor: 'transparent', }, }); function computeNewSelected( gestureState: Object, ): ?number { var {dx, dy} = gestureState; var minDist = Infinity; var newSelected = null; var pointRadius = Math.sqrt(dx * dx + dy * dy); if (Math.abs(RADIUS - pointRadius) < 80) { BOBBLE_SPOTS.forEach((spot, idx) => { var delta = {x: spot.x - dx, y: spot.y - dy}; var dist = delta.x * delta.x + delta.y * delta.y; if (dist < minDist) { minDist = dist; newSelected = idx; } }); } return newSelected; } function randColor(): string { var colors = [0,1,2].map(() => Math.floor(Math.random() * 150 + 100)); return 'rgb(' + colors.join(',') + ')'; } var BOBBLE_IMGS = [ 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xpf1/t39.1997-6/10173489_272703316237267_1025826781_n.png', 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/l/t39.1997-6/p240x240/851578_631487400212668_2087073502_n.png', 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/p240x240/851583_654446917903722_178118452_n.png', 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/p240x240/851565_641023175913294_875343096_n.png', 'https://scontent-sea1-1.xx.fbcdn.net/hphotos-xaf1/t39.1997-6/851562_575284782557566_1188781517_n.png', ]; module.exports = AnExBobble;