jeanregisser / react-native-slider

A pure JavaScript <Slider> component for react-native
MIT License
1.3k stars 573 forks source link

Getting custom Animations to work #72

Open nuttylord opened 7 years ago

nuttylord commented 7 years ago

I have a very heavily edited version of react-native-slider that i am using to create a slider with custom images. So far i have a background image with two images acting as the thumb of the slider however i want to apply an animation 'onSlidingComplete' to one of the images.

Please note that i have edited the slider.js file within node_modules to get my desired effects and i have managed to get everything working up until now but i cannot for the life of me make any animations work.

slider declaration:

             <Slider

                style={styles.slider}

                trackStyle={styles.trackStyle}

                maximumValue={this.props.max}

                minimumValue={this.props.min}

                step={this.props.step}

                value={this.state.value}

                thumbTouchSize={this.props.thumbTouchSize}

                sliderBg={require('../../images/tank_bg.png')}

                thumbImage={require('../../images/thumb_slider.png')}

                thumbImageBg={require('../../images/thumb_slider_whitebg_line.png')}

                onSlidingComplete={(value) => this.onSlidingComplete(value)}

                onValueChange={(value) => this.onValueChange(value)}

                orientation={'horizontal'}

                sliderAnim={this.state.sliderAnim}

            />

This is how i have my slider.js set up.

the places to note that i have already tried to implement animations are the _setCurrentValueAnimated function on line 577 and the _animateSlider function on line 240.

What i am looking to do is pass a boolean into the slider through its declaration as a this.state.sliderAnim: proptypes.bool and then have the code execute a scripted animation.

All i am trying to do is make the thumbImageBg animate its height from 0 to 100% once each time onSlidingComplete is called.

Any help is appreciated a lot

                    `
        import React, {
          Component,
        } from "react";
        import {
          Animated,
          StyleSheet,
          Image,
          PanResponder,
          View,
          Easing,
          Dimensions,
          LayoutAnimation,
          TouchableWithoutFeedback,
          TouchableOpacity,
          Platform,
          UIManager
        } from "react-native";

        import PropTypes from 'prop-types';

        if (Platform.OS === 'android') {
          UIManager.setLayoutAnimationEnabledExperimental(true);
        };

        const shallowCompare = require('react-addons-shallow-compare'),
              styleEqual = require('style-equal');

        const DIM = Dimensions.get('window');

        var TRACK_SIZE = 4;
        var THUMB_SIZE = 20;

        Image.getSize('../../images/tank_bg.png', (width, height) => {this._setWH({width, height})});

        function _setWH(width, height) {
            this.IMPwidth = width;
            this.IMPheight = height;
        };

        function Rect(x, y, width, height) {
          this.x = x; // This is a huge edit x = y
          this.y = y; // This is a huge edit y = x
          this.width = width;
          this.height = height;
        }

        Rect.prototype.containsPoint = function(x, y) {
          return (x >= this.x
                  && y >= this.y
                  && x <= this.x + this.width
                  && y <= this.y + this.height);
        };

        var DEFAULT_ANIMATION_CONFIGS = {
          spring : {
            friction : 7,
            tension  : 100
          },
          timing : {
            duration : 150,
            easing   : Easing.inOut(Easing.ease),
            delay    : 0
          },
          // decay : { // This has a serious bug
          //   velocity     : 1,
          //   deceleration : 0.997
          // }
        };

        export default class Slider extends Component {
          static propTypes = {
            /**
             * Initial value of the slider. The value should be between minimumValue
             * and maximumValue, which default to 0 and 1 respectively.
             * Default value is 0.
             *
             * *This is not a controlled component*, e.g. if you don't update
             * the value, the component won't be reset to its inital value.
             */
            value: PropTypes.number,

            /**
             * If true the user won't be able to move the slider.
             * Default value is false.
             */
            disabled: PropTypes.bool,

            /**
             * Initial minimum value of the slider. Default value is 0.
             */
            minimumValue: PropTypes.number,

            /**
             * Initial maximum value of the slider. Default value is 1.
             */
            maximumValue: PropTypes.number,

            /**
             * Step value of the slider. The value should be between 0 and
             * (maximumValue - minimumValue). Default value is 0.
             */
            step: PropTypes.number,

            /**
             * The color used for the track to the left of the button. Overrides the
             * default blue gradient image.
             */
            minimumTrackTintColor: PropTypes.string,

            /**
             * The color used for the track to the right of the button. Overrides the
             * default blue gradient image.
             */
            maximumTrackTintColor: PropTypes.string,

            /**
             * The color used for the thumb.
             */
            thumbTintColor: PropTypes.string,

            /**
             * The size of the touch area that allows moving the thumb.
             * The touch area has the same center has the visible thumb.
             * This allows to have a visually small thumb while still allowing the user
             * to move it easily.
             * The default is {width: 40, height: 40}.
             */
            thumbTouchSize: PropTypes.shape(
              {width: PropTypes.number, height: PropTypes.number}
            ),

            /**
             * Callback continuously called while the user is dragging the slider.
             */
            onValueChange: PropTypes.func,

            /**
             * Callback called when the user starts changing the value (e.g. when
             * the slider is pressed).
             */
            onSlidingStart: PropTypes.func,

            /**
             * Callback called when the user finishes changing the value (e.g. when
             * the slider is released).
             */
            onSlidingComplete: PropTypes.func,

            /**
             * The style applied to the slider container.
             */
            style: View.propTypes.style,

            /**
             * The style applied to the track.
             */
            trackStyle: View.propTypes.style,

            /**
             * The style applied to the thumb.
             */
            thumbStyle: View.propTypes.style,

            /**
             * Sets an image for the thumb.
             */
            thumbImage: Image.propTypes.source,

            /**
             * Set this to true to visually see the thumb touch rect in green.
             */
            debugTouchArea: PropTypes.bool,

            /**
            * Set to true to animate values with default 'timing' animation type
            */
            animateTransitions : PropTypes.bool,

            /**
            * Custom Animation type. 'spring' or 'timing'.
            */
            animationType : PropTypes.oneOf(['spring', 'timing']),

            /**
            * Used to configure the animation parameters.  These are the same parameters in the Animated library.
            */
            animationConfig : PropTypes.object,

            /**
            * Used to change the slider orientation between horizontal and vertical
            */
            orientation : PropTypes.oneOf(['horizontal', 'vertical']),

            /**
             * Sets an image for the background of the slider.
             */
            sliderBg: Image.propTypes.source,

            /**
             * Sets an image for the thumb background used for animation. @@@@ CUSTOM
             */
            thumbImageBg: Image.propTypes.source,

            /**
             * Starts the animation for the slider bar.
             */
            sliderAnim: PropTypes.bool,
          };

          state = {
            containerSize: {width: 0, height: 0},
            trackSize: {width: 0, height: 0},
            thumbSize: {width: 0, height: 0},
            allMeasured: false,
            value: new Animated.Value(this.props.value),
            imageBGStyle: {
              height: this.props.thumbImageBg.height,
            }
          };

          static defaultProps = {
            value: 0,
            minimumValue: 0,
            maximumValue: 1,
            step: 0,
            minimumTrackTintColor: '#3f3f3f',
            maximumTrackTintColor: '#b3b3b3',
            thumbTintColor: '#343434',
            thumbTouchSize: {width: 80, height: 80},
            debugTouchArea: true,
            animationType: 'timing',
            orientation: 'horizontal',
            sliderAnim: true,
          };

          _animateSlider() { // #################################################################
            // I USED THIS METHOD TO TRY CREATE AN ANIMATION USING LAYOUTANIMATION
            LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
            this.setState({
              imageBGStyle: {
                height: '100%',
              }
            })
            LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
            setInterval(function() {this.setState({
              imageBGStyle: {
                height: '0%',
              }
            })}.bind(this),5000)
          }

          componentWillMount() {
            this._panResponder = PanResponder.create({
              onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
              onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
              onPanResponderGrant: this._handlePanResponderGrant,
              onPanResponderMove: this._handlePanResponderMove,
              onPanResponderRelease: this._handlePanResponderEnd,
              onPanResponderTerminationRequest: this._handlePanResponderRequestEnd,
              onPanResponderTerminate: this._handlePanResponderEnd,
            });

            if(this.sliderAnim == true)
            {
              this.animatedValue = new Animated.Value(100);
              alert('sliderAnim is true and runs');
              console.log('sliderAnim is true and runs');
            }
          };

          componentWillReceiveProps(nextProps) {
            var newValue = nextProps.value;

            if (this.props.value !== newValue) {
              if (this.props.animateTransitions) {
                this._setCurrentValueAnimated(newValue);
              }
              else {
                this._setCurrentValue(newValue);
              }
            }
          };

          render() {
            var {
              minimumValue,
              maximumValue,
              minimumTrackTintColor,
              maximumTrackTintColor,
              thumbTintColor,
              thumbImage,
              styles,
              style,
              trackStyle,
              thumbStyle,
              debugTouchArea,
              orientation,
              sliderBg,
              thumbImageBg,
              sliderAnim,
              ...other
            } = this.props;
            var {value, containerSize, trackSize, thumbSize, allMeasured} = this.state;
            var mainStyles = styles || defaultStyles;
            var outputRange;
            if (orientation === 'horizontal') {
              outputRange = [0, containerSize.width - thumbSize.width];
            } else {
              outputRange = [containerSize.height - thumbSize.height, 0];
            }
            var thumbStart = value.interpolate({
                inputRange: [minimumValue, maximumValue],
                outputRange: outputRange,
                //extrapolate: 'clamp',
              });
            var valueVisibleStyle = {};
            if (!allMeasured) {
              valueVisibleStyle.opacity = 0;
            }

            var minimumTrackStyle = {
              position: 'absolute',
              // width: Animated.add(thumbStart, thumbSize.width/2),
              backgroundColor: minimumTrackTintColor,
              ...valueVisibleStyle
            };

            if (orientation === 'horizontal') {
              minimumTrackStyle.width = Animated.add(thumbStart, thumbSize.width / 2);
              minimumTrackStyle.marginTop = -trackSize.height;
            } else {
              minimumTrackStyle.marginLeft = -trackSize.width;
              minimumTrackStyle.top = thumbStart;
              minimumTrackStyle.height = Animated.add(thumbStart, -trackSize.height);
              minimumTrackStyle.height = Animated.multiply(minimumTrackStyle.height, -1);
            }

            var touchOverflowStyle = this._getTouchOverflowStyle();

            let imageBGStyle = [defaultStyles.thumbImgStyleBg, this.state.imageBGStyle];

            return (
              <View {...other} style={[mainStyles.container, style]} onLayout={this._measureContainer}>
                <Image style={defaultStyles.trackImgStyle} source={this.props.sliderBg}>
                  <View
                    style={[{backgroundColor: maximumTrackTintColor,}, mainStyles.track, trackStyle]}
                    renderToHardwareTextureAndroid={true}
                    onLayout={this._measureTrack} />

                    {/*LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);*/}

                  {/*<Animated.View
                    renderToHardwareTextureAndroid={true}
                    style={[mainStyles.track, trackStyle, minimumTrackStyle]} >
                    </Animated.View>*/}

                    {/*<TouchableOpacity
                      style={[defaultStyles.touchArea, {zIndex: 100}]}
                      onPress={this.animateSlider.bind(this)}>*/}

                  <Animated.View
                    onLayout={this._measureThumb}
                    renderToHardwareTextureAndroid={true}
                    style={[

                      mainStyles.thumb, thumbStyle,
                      {
                        transform: [
                          { translateX: thumbStart },
                          { translateY: 0 }
                        ],
                        ...valueVisibleStyle
                      }
                    ]} 
                  >

                      <Animated.Image 
                      style={imageBGStyle} 
                      source={this.props.thumbImageBg} >
                        <Animated.Image 
                        style={defaultStyles.thumbImgStyle} 
                        source={this.props.thumbImage}/>
                      </Animated.Image>
                  </Animated.View>
                    {/*</TouchableOpacity>*/}

                  <View
                    renderToHardwareTextureAndroid={true}
                    style={[defaultStyles.touchArea, touchOverflowStyle]}
                    {...this._panResponder.panHandlers}>
                    {debugTouchArea == true && this._renderDebugThumbTouchRect(thumbStart)}
                  </View>
                </Image>
              </View>
            );
          };

          _animate() { 
            Animated.timing(this.animatedValue,
            {
              toValue: 50,
              duration: 5000,
              easing: Easing.bounce
            }).start()
          };

          _getPropsForComponentUpdate(props) {
            var {
              value,
              onValueChange,
              onSlidingStart,
              onSlidingComplete,
              style,
              trackStyle,
              thumbStyle,
              ...otherProps,
            } = props;

            return otherProps;
          };

          _handleStartShouldSetPanResponder = (e: Object, /*gestureState: Object*/): boolean => {
            // Should we become active when the user presses down on the thumb?
            return this._thumbHitTest(e);
          };

          _handleMoveShouldSetPanResponder(/*e: Object, gestureState: Object*/): boolean {
            // Should we become active when the user moves a touch over the thumb?
            return false;
          };

          _handlePanResponderGrant = (/*e: Object, gestureState: Object*/) => {
            this._previousStart = this._getThumbStart(this._getCurrentValue());
            this._fireChangeEvent('onSlidingStart');
          };
          _handlePanResponderMove = (e: Object, gestureState: Object) => {
            if (this.props.disabled) {
              return;
            }

            this._setCurrentValue(this._getValue(gestureState));
            this._fireChangeEvent('onValueChange');
          };
          _handlePanResponderRequestEnd(e: Object, gestureState: Object) {
            // Should we allow another component to take over this pan?
            return false;
          };
          _handlePanResponderEnd = (e: Object, gestureState: Object) => {
            if (this.props.disabled) {
              return;
            }

            console.log("holy fuck");

            this._setCurrentValue(this._getValue(gestureState));
            this._fireChangeEvent('onSlidingComplete');
            if(this.sliderAnim === true)
            {
              this._animateSlider();
            }
          };

          onSlidingComplete() {
            alert("nah this works!");
          }

          _measureContainer = (x: Object) => {
            this._handleMeasure('containerSize', x);
          };

          _measureTrack = (x: Object) => {
            this._handleMeasure('trackSize', x);
          };

          _measureThumb = (x: Object) => {
            this._handleMeasure('thumbSize', x);
          };

          _handleMeasure = (name: string, x: Object) => {
            var {width, height} = x.nativeEvent.layout;
            var size = {width: width, height: height};

            var storeName = `_${name}`;
            var currentSize = this[storeName];
            if (currentSize && width === currentSize.width && height === currentSize.height) {
              return;
            }
            this[storeName] = size;

            if (this._containerSize && this._trackSize && this._thumbSize) {
              this.setState({
                containerSize: this._containerSize,
                trackSize: this._trackSize,
                thumbSize: this._thumbSize,
                allMeasured: true,
              })
            }
          };

          _getRatio = (value: number) => {
            return (value - this.props.minimumValue) / (this.props.maximumValue - this.props.minimumValue);
          };

          _getThumbStart = (value: number) => {
            var ratio = this._getRatio(value);

            var start = 0;

            if (this.props.orientation === 'horizontal') {
              var length = this.state.containerSize.width - this.state.thumbSize.width;
              start = ratio * length;
            } else {
              var length = this.state.containerSize.height - this.state.thumbSize.height;
              start = length - (ratio * length);
            }

            return start;
          };

          _getValue = (gestureState: Object) => {
            var length = 0;

            if (this.props.orientation === 'horizontal') {
              length = this.state.containerSize.width - this.state.thumbSize.width;
            } else {
              length = this.state.containerSize.height - this.state.thumbSize.height;
            }

            var thumbStart = this._previousStart;

            var ratio;
            /*
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                THE NEXT STATEMENT IS EDITED TO AUTOMATICALLY TAKE ROTATED INPUTS FROM THE USER. EDIT IF "ORIENTATION" WORKS!

              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            */
            if (this.props.orientation === 'horizontal') {
              thumbStart += -gestureState.dy;
              ratio = (thumbStart / length);
            } else {
              thumbStart += gestureState.dy;
              ratio = 1 - (thumbStart / length);
            }

            if (this.props.step) {
              return Math.max(this.props.minimumValue,
                Math.min(this.props.maximumValue,
                  this.props.minimumValue + Math.round(ratio * (this.props.maximumValue - this.props.minimumValue) / this.props.step) * this.props.step
                )
              );
            } else {
              return Math.max(this.props.minimumValue,
                Math.min(this.props.maximumValue,
                  ratio * (this.props.maximumValue - this.props.minimumValue) + this.props.minimumValue
                )
              );
            }
          };

          _getCurrentValue = () => {
            return this.state.value.__getValue();
          };

          _setCurrentValue = (value: Number) => {
            this.state.value.setValue(value);
          };

          _setCurrentValueAnimated = (value: Number) => {
            var animationType   = this.props.animationType;
            var animationConfig = Object.assign(
                  {},
                  DEFAULT_ANIMATION_CONFIGS[animationType],
                  this.props.animationConfig,
                  {toValue : value}
                );
                // #######################################################################################################################
                // I CHANGED THIS THIS.STATE IN THE NEXT LINE SO THAT IT WOULD CHANGE THE HEIGHT OF MY IMAGES STYLE IN AN ANIMATION BUT IT DID NOT WORK
            Animated[animationType](this.state.imageBGStyle.height, animationConfig).start();
          };

          _fireChangeEvent = (event) => {
            if (this.props[event]) {
              this.props[event](this._getCurrentValue());
            }
          };

          _getTouchOverflowSize = () => {
            var state = this.state;
            var props = this.props;

            var size = {};
            if (state.allMeasured === true) {

              if (props.orientation === 'horizontal') {
                size.width = Math.max(0, props.thumbTouchSize.width - state.thumbSize.width);
                size.height = Math.max(0, props.thumbTouchSize.height - state.containerSize.height);
              } else {
                size.width = Math.max(0, props.thumbTouchSize.width - state.containerSize.width);
                size.height = Math.max(0, props.thumbTouchSize.height - state.thumbSize.height);
              }
            }

            return size;
          };

          _getTouchOverflowStyle = () => {
            var {width, height} = this._getTouchOverflowSize();

            var touchOverflowStyle = {};
            if (width !== undefined && height !== undefined) {
              var verticalMargin = -height / 2;
              touchOverflowStyle.marginTop = verticalMargin;
              touchOverflowStyle.marginBottom = verticalMargin;

              var horizontalMargin = -width / 2;
              touchOverflowStyle.marginLeft = horizontalMargin;
              touchOverflowStyle.marginRight = horizontalMargin;
            }

            if (this.props.debugTouchArea === true) {
              touchOverflowStyle.backgroundColor = 'orange';
              touchOverflowStyle.opacity = 0.5;
            }

            return touchOverflowStyle;
          };

          _thumbHitTest = (e: Object) => {
            var nativeEvent = e.nativeEvent;
            var thumbTouchRect = this._getThumbTouchRect();

            return thumbTouchRect.containsPoint(nativeEvent.locationX, nativeEvent.locationY);
          };

          _getThumbTouchRect = () => {
            var state = this.state;
            var props = this.props;
            var touchOverflowSize = this._getTouchOverflowSize();

            var rect = new Rect(
              0,
              0,
              props.thumbTouchSize.width,
              state.containerSize.height
            );

            if (this.props.orientation === 'horizontal') {
              rect.x = touchOverflowSize.width / 2 + this._getThumbStart(this._getCurrentValue()) + (state.thumbSize.width - props.thumbTouchSize.width) / 2;
              rect.y = touchOverflowSize.height / 2 + (state.containerSize.height - props.thumbTouchSize.height) / 2;
            } else {
              rect.x = touchOverflowSize.width / 2 + (state.containerSize.width - props.thumbTouchSize.width) / 2;
              rect.y = touchOverflowSize.height / 2 + this._getThumbStart(this._getCurrentValue()) + (state.thumbSize.height - props.thumbTouchSize.height) / 2;
            }

            return rect;
          };

          _renderDebugThumbTouchRect = (thumbStart) => {
            var thumbTouchRect = this._getThumbTouchRect();

            var positionStyle = {
              left: thumbTouchRect.x,
              top: thumbTouchRect.y,
              width: thumbTouchRect.width,
              height: thumbTouchRect.height,
              flex: 1,
              flexDirection: 'column',
              justifyContent: 'center'
            };

            if (this.props.orientation === 'horizontal') {
              positionStyle.left = thumbStart;
            } else {
              positionStyle.top = thumbStart;
            }
            return (
              <Animated.View
                style={[defaultStyles.debugThumbTouchArea, positionStyle]}
                pointerEvents='none'
              />
            );
          };

          _renderThumbImage = () => {
            var {thumbImage} = this.props;

            if (!thumbImage) return; 

            return {thumbImage};
          };
        };

        var defaultStyles = StyleSheet.create({
          container: {
            // height: 440,
            justifyContent: 'center',

          },
          track: {
            height: TRACK_SIZE,
            borderRadius: TRACK_SIZE / 2,

          },
          thumb: {
            position: 'absolute',
            width: THUMB_SIZE,
            height: '100%',
            borderRadius: THUMB_SIZE / 2,

          },
          thumbImgStyle: {
            resizeMode: 'contain',
            width: '100%',
            height: '100%',
            backgroundColor:'transparent'
          },
          thumbImgStyleBg: {
            resizeMode: 'contain',
            width: '100%',
            height: '100%',
            backgroundColor:'transparent'
          },
          trackImgStyle: {
            resizeMode: 'contain',
            flex: 1,
            width: '100%',
            height: '100%',
          },
          touchArea: {
            position: 'absolute',
            backgroundColor: 'transparent',
            top: '-40%',
            left: 0,
            right: 0,
            bottom: '-40%',
          },
          debugThumbTouchArea: {
            position: 'absolute',
            backgroundColor: 'green',
            opacity: 0.5,

          }
        });`
jquartz commented 5 years ago

Did you manage to sort this issue?

Trying to do a similar thing with the slider now in 'react-native'