react-native#PanResponder JavaScript Examples

The following examples show how to use react-native#PanResponder. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.js    From react-native-bitmap-color-picker with MIT License 7 votes vote down vote up
/**
 * Simplified pan responder wrapper.
 */
export function createPanResponder({ onStart = fn, onMove = fn, onEnd = fn }) {
  return PanResponder.create({
    onStartShouldSetPanResponder: fn,
    onStartShouldSetPanResponderCapture: fn,
    onMoveShouldSetPanResponder: fn,
    onMoveShouldSetPanResponderCapture: fn,
    onPanResponderTerminationRequest: fn,
    onPanResponderGrant: (evt, state) => {
      return onStart({ x: evt.nativeEvent.pageX, y: evt.nativeEvent.pageY }, evt, state, 'start')
    },
    onPanResponderMove: (evt, state) => {
      return onMove({ x: evt.nativeEvent.pageX, y: evt.nativeEvent.pageY }, evt, state, 'move')
    },
    onPanResponderRelease: (evt, state) => {
      return onEnd({ x: evt.nativeEvent.pageX, y: evt.nativeEvent.pageY }, evt, state, 'end')
    },
  })
}
Example #2
Source File: OBSlidingPanel.js    From haven with MIT License 6 votes vote down vote up
constructor(props) {
    super(props);
    const { height = DEFAULT_CONTENT_WRAPPER_HEIGHT } = props;
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        const { dx, dy } = gestureState;
        return dx > 10 || dx < -10 || dy > 10 || dy < -10;
      },
      onMoveShouldSetPanResponderCapture: () => true,
      onPanResponderMove: (evt, gestureState) => {
        const { dy } = gestureState;
        if (dy >= 0) {
          this.verticalAniVal.setValue(0 - dy);
        }
      },
      onPanResponderRelease: (evt, gestureState) => {
        const { dy } = gestureState;
        if (dy >= 100) {
          this.disappear();
        } else {
          this.resetVericalPosition();
        }
      },
      onShouldBlockNativeResponder: () => true,
    });
    this.verticalAniVal = new Animated.Value(-height);
  }
Example #3
Source File: HDWalletComponent.js    From RRWallet with MIT License 6 votes vote down vote up
constructor(props) {
    super(props);
    this._panResponder = PanResponder.create({
      onPanResponderMove: this._onPanResponderMove,
    });
    DeviceEventEmitter.addListener(WALLET_TAB_JUMP_NOTIFICATION, ({ index }) => {
      if (index !== WALLET_TAB_JUMP_NOTIFICATION_INDEX_HD) {
        return;
      }
      this.props.jumpTo && this.props.jumpTo("hd");
    });
  }
Example #4
Source File: MultiSigWalletComponent.js    From RRWallet with MIT License 6 votes vote down vote up
constructor(props) {
    super(props);
    this._panResponder = PanResponder.create({
      onPanResponderMove: this._onPanResponderMove,
    });
    DeviceEventEmitter.addListener(WALLET_TAB_JUMP_NOTIFICATION, ({ index }) => {
      if (index !== WALLET_TAB_JUMP_NOTIFICATION_INDEX_MULTISIG) {
        return;
      }
      this.props.jumpTo && this.props.jumpTo("multisig");
    });
  }
Example #5
Source File: ImageCropOverlay.js    From react-native-expo-image-cropper with MIT License 6 votes vote down vote up
UNSAFE_componentWillMount() {
        this.panResponder = PanResponder.create({
            onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
            onPanResponderGrant: this.handlePanResponderGrant,
            onPanResponderMove: this.handlePanResponderMove,
            onPanResponderRelease: this.handlePanResponderEnd,
            onPanResponderTerminate: this.handlePanResponderEnd,
        })
    }
Example #6
Source File: index.js    From react-native-gesture-bottom-sheet with MIT License 6 votes vote down vote up
createPanResponder(props) {
    const { height } = props;
    const { pan } = this.state;
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (e, gestureState) => {
        if (gestureState.dy > 0) {
          Animated.event([null, { dy: pan.y }], {
            useNativeDriver: false,
          })(e, gestureState);
        }
      },
      onPanResponderRelease: (e, gestureState) => {
        const gestureLimitArea = height / 3;
        const gestureDistance = gestureState.dy;
        if (gestureDistance > gestureLimitArea) {
          this.setModalVisible(false);
        } else {
          Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false, }).start();
        }
      },
    });
  }
Example #7
Source File: SliderPickerCursor.js    From react-native-slider-picker with GNU General Public License v3.0 5 votes vote down vote up
constructor(props) {
    super(props);

    // Props checking
    this.buttonBackgroundColor =  this.props.buttonBackgroundColor ? this.props.buttonBackgroundColor : 'white';
    this.buttonBorderColor =  this.props.buttonBorderColor ? this.props.buttonBorderColor : 'dimgrey';
    this.buttonBorderWidth = this.props.buttonBorderWidth ? this.props.buttonBorderWidth : 1;
    this.buttonDimensionsPercentage = this.props.buttonDimensionsPercentage ? this.props.buttonDimensionsPercentage : 6;
    this.defaultValue = this.props.defaultValue;
    this.maxOffset = this.props.maxOffset ? this.props.maxOffset : vw(85);
    this.maxValue = this.props.maxValue ?  this.props.maxValue : 10;
    this.releaseCallback = this.props.releaseCallback ? this.props.releaseCallback : () => {};
    this.slideBeginCallback = this.props.slideBeginCallback ? this.props.slideBeginCallback : () => {};
    this.errorToleranceMargin = this.props.errorToleranceMargin ? this.props.errorToleranceMargin : null;

    // Check override style props
    this.buttonStylesOverride = this.props.buttonStylesOverride ? this.props.buttonStylesOverride : null;

    // Set buttonWidth to width passed for dimensions by default
    this.buttonWidth = vw(this.buttonDimensionsPercentage);

    // If button styles have been override and the override styles have a width property
    if ( this.buttonStylesOverride && Object.keys(this.buttonStylesOverride).includes('width') ) {
      // Set buttonWidth to the value passed in styles override.
      this.buttonWidth = this.buttonStylesOverride['width'];
    }

    // Make sure that defaultValue isn't out of range
    if (this.defaultValue > this.maxValue) {
      this.defaultValue = this.maxValue;
    }
    
    // Initialize empty array to store xOffsets in
    this.offsetsMap = [];

    // Get x-axis positioning of each number/separator
    for (let i = 0; i <= this.maxValue; i++) {
      this.offsetsMap.push({
        offset: this.maxOffset * (i / this.maxValue),
        value: i
      });
    }

    // Initialize state
    this.state = {
      // Create instance of Animated.XY, which interpolates X and Y values, in our case, we'll only need the X value.
      drag: new Animated.ValueXY(),
      // used to reference latestPosition of draggable view. Updated onPanResponderRelease.
      latestPosition: this.getOffsetPosition(this.defaultValue),
      // Used to set state on drag if user drags past Y axis threshold.
      xOffsetAtToleranceMarginSurpassed: null
    };

    // Initialize value to accomodate for width of button
    this.state.drag.setValue({ 
      x: this.getOffsetPosition(this.defaultValue) - (this.buttonWidth * .5),
      y: 0 
    });

    // Create panResponder, which is responsible for the dragging
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder : () => true,
      onPanResponderGrant: (evt, gesture) => {
        this.panResponderGrantHandler()
      },
      onPanResponderMove: (evt, gesture) => { // When user moves cursor/button
        this.panResponderMoveHandler(gesture);
      },
      onPanResponderRelease: (evt, gesture) => { // When user press/drag ends
        this.panResponderReleaseHandler(gesture)
      },
      onPanResponderTerminate: (evt, gesture) => { // When user's touch/gesture is move outside of the button/cursor of <SliderPickerCursor>
        this.panResponderReleaseHandler(gesture);
      }
    });
  }
Example #8
Source File: DisplayedElement.js    From UltimateApp with MIT License 5 votes vote down vote up
/* Props must contain:
      - type: which indicates how to display the element: "offense", "defense", "triangle" or "disc"
      - editable: true if element can be moved by the user
      - number: string defined if there is something written on the element
      - eId: element index in the list of elements of the drill (-1 if it is not currently in a drill)
    */
  constructor(props) {
    super(props);

    this.state = {
      isMoving: false,
      stateFromProps: _initializeStateFromProps(props),
    };

    this.offset = new Animated.ValueXY({ x: 0, y: 0 });

    // Initiate the panResponder
    this.panResponder = PanResponder.create({
      // Ask to be the responder
      onStartShouldSetPanResponder: () => true,

      // Called when the gesture starts
      onPanResponderGrant: () => {
        if (this.props.onMoveStart !== undefined && this.props.onMoveStart !== null) this.props.onMoveStart();

        if (this.props.editable) {
          this.setState({
            isMoving: true,
          });
          this.offset.setOffset({
            x: this.state.stateFromProps.interpolateX.__getValue(),
            y: this.state.stateFromProps.interpolateY.__getValue(),
          });

          this.offset.setValue({ x: 0, y: 0 });
        }
      },

      onPanResponderMove: this.props.editable
        ? Animated.event([null, { dx: this.offset.x, dy: this.offset.y }], { useNativeDriver: false })
        : undefined,

      onPanResponderRelease: (event, gestureState) => {
        if (this.props.editable && this.props.onMoveEnd !== undefined && this.props.onMoveEnd !== null) {
          this.props.onMoveEnd(this.props.eId, this.props.type, gestureState.dx, gestureState.dy);
        }
        this.setState({
          isMoving: false,
        });
      },
    });
  }
Example #9
Source File: MovingCircle.js    From UltimateApp with MIT License 5 votes vote down vote up
MovingCircle = (props) => {
  const currentPosition = new Animated.ValueXY({ x: 0, y: 0 });

  let _val = { x: 0, y: 0 };

  currentPosition.addListener((value) => (_val = value)); // Initialize PanResponder with move handling

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,

    onPanResponderGrant: () => {
      currentPosition.setOffset({
        x: _val.x,
        y: _val.y,
      });

      currentPosition.setValue({ x: 0, y: 0 });
    },

    onPanResponderMove: Animated.event([null, { dx: currentPosition.x, dy: currentPosition.y }], {
      useNativeDriver: false,
    }),

    onPanResponderRelease: (evt, gesturestate) => {
      props.onMoveEnd(props.elemId, currentPosition.x._value, currentPosition.y._value, props.isCounterCut);

      // Avoid a bug where the current position is added to the next MovingCircle
      currentPosition.setValue({ x: 0, y: 0 });
    },
  });

  return (
    <Animated.View
      {...panResponder.panHandlers}
      style={[
        styles.circle,
        {
          transform: currentPosition.getTranslateTransform(),
          left: props.cx - props.radius,
          top: props.cy - props.radius,
        },
      ]}
      height={2 * props.radius}
      width={2 * props.radius}
    />
  );
}
Example #10
Source File: DraggableDisplayedElement.js    From UltimateApp with MIT License 5 votes vote down vote up
DraggableDisplayedElement = (props) => {
  const { draggableBaseWidth, type, number } = props;

  /* Current position of the element in pixels */
  const currentPosition = new Animated.ValueXY({ x: 0, y: 0 });

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,

    onPanResponderMove: Animated.event([null, { dx: currentPosition.x, dy: currentPosition.y }], {
      useNativeDriver: false,
    }),

    onPanResponderRelease: (event, gestureState) => {
      props.onMoveEnd(type, gestureState.moveX, gestureState.moveY);
      currentPosition.setValue({ x: 0, y: 0 });
    },
  });

  const panStyle = {
    transform: currentPosition.getTranslateTransform(),
    padding: 5,
    height: '100%',
    flexBasis: '25%',
    minWidth: draggableBaseWidth,
    minHeight: draggableBaseWidth,
    alignItems: 'center',
    justifyContent: 'center',
  };

  return (
    <Animated.View {...panResponder.panHandlers} style={panStyle} key={type}>
      {
        {
          defense: <Player baseWidth={draggableBaseWidth} number={number} type={type} />,
          offense: <Player baseWidth={draggableBaseWidth} number={number} type={type} />,
          triangle: <Cone baseWidth={draggableBaseWidth} number={number} />,
          disc: <Disc baseWidth={draggableBaseWidth} number={number} />,
        }[type]
      }
    </Animated.View>
  );
}
Example #11
Source File: Holder.js    From react-native-drag-text-editor with MIT License 5 votes vote down vote up
constructor(props) {
    super(props);

    this.position = {
      x: 0,
      y: 0,
    };

    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (event, gestureState) => {return( gestureState.dx != 0 && gestureState.dy != 0)},
      onMoveShouldSetPanResponderCapture: (event, gestureState) => { return (gestureState.dx != 0 && gestureState.dy != 0)},
      onPanResponderGrant: (event, gestureState) => {
      const {
        onStart
        }=this.props;
         
         this.position = {
          x: 0,
          y: 0,
        };
     onStart([
          0,
          0,
        ]);
      },
      onPanResponderMove: (event, gestureState) => {
      const {
        onMove
        }=this.props;
      
       onMove( 
         [
          gestureState.dx - this.position.x,
          gestureState.dy - this.position.y,
        ]);

        this.position = {
          x: gestureState.dx,
          y: gestureState.dy,
        };
      },
      onPanResponderTerminationRequest: (event, gestureState) => true,
      onPanResponderRelease: (event, gestureState) => {
      const {
        onEnd
        }=this.props;
      
        onEnd([
          gestureState.moveX,
          gestureState.moveY,
        ]);
      },
      });
  }
Example #12
Source File: index.js    From the-eye-knows-the-garbage with MIT License 4 votes vote down vote up
Swipeout = createReactClass({
  mixins: [tweenState.Mixin],

  propTypes: {
    autoClose: PropTypes.bool,
    backgroundColor: PropTypes.string,
    close: PropTypes.bool,
    left: PropTypes.array,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    right: PropTypes.array,
    scroll: PropTypes.func,
    style: (ViewPropTypes || View.propTypes).style,
    sensitivity: PropTypes.number,
    buttonWidth: PropTypes.number,
    disabled: PropTypes.bool,
  },

  getDefaultProps: function () {
    return {
      disabled: false,
      rowID: -1,
      sectionID: -1,
      sensitivity: 50,
    };
  },

  getInitialState: function () {
    return {
      autoClose: this.props.autoClose || false,
      btnWidth: 0,
      btnsLeftWidth: 0,
      btnsRightWidth: 0,
      contentHeight: 0,
      contentPos: 0,
      contentWidth: 0,
      openedRight: false,
      swiping: false,
      tweenDuration: 160,
      timeStart: null,
    };
  },

  componentWillMount: function () {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (event, gestureState) => true,
      onStartShouldSetPanResponderCapture: (event, gestureState) =>
        this.state.openedLeft || this.state.openedRight,
      onMoveShouldSetPanResponderCapture: (event, gestureState) =>
        Math.abs(gestureState.dx) > this.props.sensitivity &&
        Math.abs(gestureState.dy) <= this.props.sensitivity,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminate: this._handlePanResponderEnd,
      onShouldBlockNativeResponder: (event, gestureState) => false,
      onPanResponderTerminationRequest: () => false,
    });
  },

  componentWillReceiveProps: function (nextProps) {
    if (nextProps.close) this._close();
    if (nextProps.openRight) this._openRight();
    if (nextProps.openLeft) this._openLeft();
  },

  _handlePanResponderGrant: function (e: Object, gestureState: Object) {
    if (this.props.disabled) return;
    if (!this.state.openedLeft && !this.state.openedRight) {
      this._callOnOpen();
    } else {
      this._callOnClose();
    }
    this.refs.swipeoutContent.measure((ox, oy, width, height) => {
      let buttonWidth = this.props.buttonWidth || (width / 5);
      this.setState({
        btnWidth: buttonWidth,
        btnsLeftWidth: this.props.left ? buttonWidth * this.props.left.length : 0,
        btnsRightWidth: this.props.right ? buttonWidth * this.props.right.length : 0,
        swiping: true,
        timeStart: (new Date()).getTime(),
      });
    });
  },

  _handlePanResponderMove: function (e: Object, gestureState: Object) {
    if (this.props.disabled) return;
    var posX = gestureState.dx;
    var posY = gestureState.dy;
    var leftWidth = this.state.btnsLeftWidth;
    var rightWidth = this.state.btnsRightWidth;
    if (this.state.openedRight) var posX = gestureState.dx - rightWidth;
    else if (this.state.openedLeft) var posX = gestureState.dx + leftWidth;

    //  prevent scroll if moveX is true
    var moveX = Math.abs(posX) > Math.abs(posY);
    if (this.props.scroll) {
      if (moveX) this.props.scroll(false);
      else this.props.scroll(true);
    }
    if (this.state.swiping) {
      //  move content to reveal swipeout
      if (posX < 0 && this.props.right) {
        this.setState({ contentPos: Math.min(posX, 0) })
      } else if (posX > 0 && this.props.left) {
        this.setState({ contentPos: Math.max(posX, 0) })
      };
    }
  },

  _handlePanResponderEnd: function (e: Object, gestureState: Object) {
    if (this.props.disabled) return;
    var posX = gestureState.dx;
    var contentPos = this.state.contentPos;
    var contentWidth = this.state.contentWidth;
    var btnsLeftWidth = this.state.btnsLeftWidth;
    var btnsRightWidth = this.state.btnsRightWidth;

    //  minimum threshold to open swipeout
    var openX = contentWidth * 0.33;

    //  should open swipeout
    var openLeft = posX > openX || posX > btnsLeftWidth / 2;
    var openRight = posX < -openX || posX < -btnsRightWidth / 2;

    //  account for open swipeouts
    if (this.state.openedRight) var openRight = posX - openX < -openX;
    if (this.state.openedLeft) var openLeft = posX + openX > openX;

    //  reveal swipeout on quick swipe
    var timeDiff = (new Date()).getTime() - this.state.timeStart < 200;
    if (timeDiff) {
      var openRight = posX < -openX / 10 && !this.state.openedLeft;
      var openLeft = posX > openX / 10 && !this.state.openedRight;
    }

    if (this.state.swiping) {
      if (openRight && contentPos < 0 && posX < 0) {
        this._open(-btnsRightWidth, 'right');
      } else if (openLeft && contentPos > 0 && posX > 0) {
        this._open(btnsLeftWidth, 'left');
      } else {
        this._close();
      }
    }

    //  Allow scroll
    if (this.props.scroll) this.props.scroll(true);
  },

  _tweenContent: function (state, endValue) {
    this.tweenState(state, {
      easing: tweenState.easingTypes.easeInOutQuad,
      duration: endValue === 0 ? this.state.tweenDuration * 1.5 : this.state.tweenDuration,
      endValue: endValue,
    });
  },

  _rubberBandEasing: function (value, limit) {
    if (value < 0 && value < limit) return limit - Math.pow(limit - value, 0.85);
    else if (value > 0 && value > limit) return limit + Math.pow(value - limit, 0.85);
    return value;
  },

  //  close swipeout on button press
  _autoClose: function (btn) {
    if (this.state.autoClose) this._close();
    var onPress = btn.onPress;
    if (onPress) onPress();
  },

  _open: function (contentPos, direction) {
    const left = direction === 'left';
    const { sectionID, rowID, onOpen } = this.props;
    onOpen && onOpen(sectionID, rowID, direction);
    this._tweenContent('contentPos', contentPos);
    this.setState({
      contentPos,
      openedLeft: left,
      openedRight: !left,
      swiping: false,
    });
  },

  _close: function () {
    const { sectionID, rowID, onClose } = this.props;
    if (onClose && (this.state.openedLeft || this.state.openedRight)) {
      const direction = this.state.openedRight ? 'right' : 'left';
      onClose(sectionID, rowID, direction);
    }
    this._tweenContent('contentPos', 0);
    this._callOnClose();
    this.setState({
      openedRight: false,
      openedLeft: false,
      swiping: false,
    });
  },

  _callOnClose: function () {
    if (this.props.onClose) this.props.onClose(this.props.sectionID, this.props.rowID);
  },

  _callOnOpen: function () {
    if (this.props.onOpen) this.props.onOpen(this.props.sectionID, this.props.rowID);
  },

  _openRight: function () {
    this.refs.swipeoutContent.measure((ox, oy, width, height) => {
      let btnWidth = this.props.buttonWidth || (width / 5);

      this.setState({
        btnWidth,
        btnsRightWidth: this.props.right ? btnWidth * this.props.right.length : 0,
      }, () => {
        this._tweenContent('contentPos', -this.state.btnsRightWidth);
        this._callOnOpen();
        this.setState({
          contentPos: -this.state.btnsRightWidth,
          openedLeft: false,
          openedRight: true,
          swiping: false
        });
      });
    });
  },

  _openLeft: function () {
    this.refs.swipeoutContent.measure((ox, oy, width, height) => {
      let btnWidth = this.props.buttonWidth || (width / 5);

      this.setState({
        btnWidth,
        btnsLeftWidth: this.props.left ? btnWidth * this.props.left.length : 0,
      }, () => {
        this._tweenContent('contentPos', this.state.btnsLeftWidth);
        this._callOnOpen();
        this.setState({
          contentPos: this.state.btnsLeftWidth,
          openedLeft: true,
          openedRight: false,
          swiping: false
        });
      });
    });
  },

  render: function () {
    var contentWidth = this.state.contentWidth;
    var posX = this.getTweeningValue('contentPos');

    var styleSwipeout = [styles.swipeout, this.props.style];
    if (this.props.backgroundColor) {
      styleSwipeout.push([{ backgroundColor: this.props.backgroundColor }]);
    }

    var limit = -this.state.btnsRightWidth;
    if (posX > 0) var limit = this.state.btnsLeftWidth;

    var styleLeftPos = {
      left: {
        left: 0,
        overflow: 'hidden',
        width: Math.min(limit * (posX / limit), limit),
      },
    };
    var styleRightPos = {
      right: {
        left: Math.abs(contentWidth + Math.max(limit, posX)),
        right: 0,
      },
    };
    var styleContentPos = {
      content: {
        transform: [{ translateX: this._rubberBandEasing(posX, limit) }],
      },
    };

    var styleContent = [styles.swipeoutContent];
    styleContent.push(styleContentPos.content);

    var styleRight = [styles.swipeoutBtns];
    styleRight.push(styleRightPos.right);

    var styleLeft = [styles.swipeoutBtns];
    styleLeft.push(styleLeftPos.left);

    var isRightVisible = posX < 0;
    var isLeftVisible = posX > 0;

    return (
      <View style={styleSwipeout}>
        <View
          ref="swipeoutContent"
          style={styleContent}
          onLayout={this._onLayout}
          {...this._panResponder.panHandlers}
        >
          {this.props.children}
        </View>
        {this._renderButtons(this.props.right, isRightVisible, styleRight)}
        {this._renderButtons(this.props.left, isLeftVisible, styleLeft)}
      </View>
    );
  },

  _onLayout: function (event) {
    var { width, height } = event.nativeEvent.layout;
    this.setState({
      contentWidth: width,
      contentHeight: height,
    });
  },

  _renderButtons: function (buttons, isVisible, style) {
    if (buttons && isVisible) {
      return (<View style={style}>
        {buttons.map(this._renderButton)}
      </View>);
    } else {
      return (
        <View />
      );
    }
  },

  _renderButton: function (btn, i) {
    return (
      <SwipeoutBtn
        backgroundColor={btn.backgroundColor}
        color={btn.color}
        component={btn.component}
        disabled={btn.disabled}
        height={this.state.contentHeight}
        key={i}
        onPress={() => this._autoClose(btn)}
        text={btn.text}
        type={btn.type}
        underlayColor={btn.underlayColor}
        width={this.state.btnWidth}
      />
    );
  }
})
Example #13
Source File: HeaderScrollableTabView.js    From react-native-collapsible-tabview with MIT License 4 votes vote down vote up
App = () => {
  /**
   * stats
   */
  const [tabIndex, setIndex] = useState(0);
  const [routes] = useState([
    {key: 'tab1', title: 'Tab1'},
    {key: 'tab2', title: 'Tab2'},
  ]);
  const [canScroll, setCanScroll] = useState(true);
  const [tab1Data] = useState(Array(40).fill(0));
  const [tab2Data] = useState(Array(30).fill(0));

  /**
   * ref
   */
  const scrollY = useRef(new Animated.Value(0)).current;
  const headerScrollY = useRef(new Animated.Value(0)).current;
  const listRefArr = useRef([]);
  const listOffset = useRef({});
  const isListGliding = useRef(false);
  const headerScrollStart = useRef(0);
  const _tabIndex = useRef(0);

  /**
   * PanResponder for header
   */
  const headerPanResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
      onStartShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        syncScrollOffset();
        return false;
      },

      onMoveShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        return Math.abs(gestureState.dy) > 5;
      },

      onPanResponderRelease: (evt, gestureState) => {
        syncScrollOffset();
        if (Math.abs(gestureState.vy) < 0.2) {
          return;
        }
        headerScrollY.setValue(scrollY._value);
        Animated.decay(headerScrollY, {
          velocity: -gestureState.vy,
          useNativeDriver: true,
        }).start(() => {
          syncScrollOffset();
        });
      },
      onPanResponderMove: (evt, gestureState) => {
        listRefArr.current.forEach((item) => {
          if (item.key !== routes[_tabIndex.current].key) {
            return;
          }
          if (item.value) {
            item.value.scrollToOffset({
              offset: -gestureState.dy + headerScrollStart.current,
              animated: false,
            });
          }
        });
      },
      onShouldBlockNativeResponder: () => true,
      onPanResponderGrant: (evt, gestureState) => {
        headerScrollStart.current = scrollY._value;
      },
    }),
  ).current;

  /**
   * PanResponder for list in tab scene
   */
  const listPanResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
      onStartShouldSetPanResponder: (evt, gestureState) => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        return false;
      },
      onShouldBlockNativeResponder: () => true,
      onPanResponderGrant: (evt, gestureState) => {
        headerScrollY.stopAnimation();
      },
    }),
  ).current;

  /**
   * effect
   */
  useEffect(() => {
    scrollY.addListener(({value}) => {
      const curRoute = routes[tabIndex].key;
      listOffset.current[curRoute] = value;
    });

    headerScrollY.addListener(({value}) => {
      listRefArr.current.forEach((item) => {
        if (item.key !== routes[tabIndex].key) {
          return;
        }
        if (value > HeaderHeight || value < 0) {
          headerScrollY.stopAnimation();
          syncScrollOffset();
        }
        if (item.value && value <= HeaderHeight) {
          item.value.scrollToOffset({
            offset: value,
            animated: false,
          });
        }
      });
    });
    return () => {
      scrollY.removeAllListeners();
      headerScrollY.removeAllListeners();
    };
  }, [routes, tabIndex]);

  /**
   *  helper functions
   */
  const syncScrollOffset = () => {
    const curRouteKey = routes[_tabIndex.current].key;

    listRefArr.current.forEach((item) => {
      if (item.key !== curRouteKey) {
        if (scrollY._value < HeaderHeight && scrollY._value >= 0) {
          if (item.value) {
            item.value.scrollToOffset({
              offset: scrollY._value,
              animated: false,
            });
            listOffset.current[item.key] = scrollY._value;
          }
        } else if (scrollY._value >= HeaderHeight) {
          if (
            listOffset.current[item.key] < HeaderHeight ||
            listOffset.current[item.key] == null
          ) {
            if (item.value) {
              item.value.scrollToOffset({
                offset: HeaderHeight,
                animated: false,
              });
              listOffset.current[item.key] = HeaderHeight;
            }
          }
        }
      }
    });
  };

  const onMomentumScrollBegin = () => {
    isListGliding.current = true;
  };

  const onMomentumScrollEnd = () => {
    isListGliding.current = false;
    syncScrollOffset();
  };

  const onScrollEndDrag = () => {
    syncScrollOffset();
  };

  /**
   * render Helper
   */
  const renderHeader = () => {
    const y = scrollY.interpolate({
      inputRange: [0, HeaderHeight],
      outputRange: [0, -HeaderHeight],
      extrapolate: 'clamp',
    });
    return (
      <Animated.View
        {...headerPanResponder.panHandlers}
        style={[styles.header, {transform: [{translateY: y}]}]}>
        <TouchableOpacity
          style={{flex: 1, justifyContent: 'center'}}
          activeOpacity={1}
          onPress={() => Alert.alert('header Clicked!')}>
          <Text>Scrollable Header</Text>
        </TouchableOpacity>
      </Animated.View>
    );
  };

  const rednerTab1Item = ({item, index}) => {
    return (
      <View
        style={{
          borderRadius: 16,
          marginLeft: index % 2 === 0 ? 0 : 10,
          width: tab1ItemSize,
          height: tab1ItemSize,
          backgroundColor: '#aaa',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <Text>{index}</Text>
      </View>
    );
  };

  const rednerTab2Item = ({item, index}) => {
    return (
      <View
        style={{
          marginLeft: index % 3 === 0 ? 0 : 10,
          borderRadius: 16,
          width: tab2ItemSize,
          height: tab2ItemSize,
          backgroundColor: '#aaa',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <Text>{index}</Text>
      </View>
    );
  };

  const renderLabel = ({route, focused}) => {
    return (
      <Text style={[styles.label, {opacity: focused ? 1 : 0.5}]}>
        {route.title}
      </Text>
    );
  };

  const renderScene = ({route}) => {
    const focused = route.key === routes[tabIndex].key;
    let numCols;
    let data;
    let renderItem;
    switch (route.key) {
      case 'tab1':
        numCols = 2;
        data = tab1Data;
        renderItem = rednerTab1Item;
        break;
      case 'tab2':
        numCols = 3;
        data = tab2Data;
        renderItem = rednerTab2Item;
        break;
      default:
        return null;
    }
    return (
      <Animated.FlatList
        // scrollEnabled={canScroll}
        {...listPanResponder.panHandlers}
        numColumns={numCols}
        ref={(ref) => {
          if (ref) {
            const found = listRefArr.current.find((e) => e.key === route.key);
            if (!found) {
              listRefArr.current.push({
                key: route.key,
                value: ref,
              });
            }
          }
        }}
        scrollEventThrottle={16}
        onScroll={
          focused
            ? Animated.event(
                [
                  {
                    nativeEvent: {contentOffset: {y: scrollY}},
                  },
                ],
                {useNativeDriver: true},
              )
            : null
        }
        onMomentumScrollBegin={onMomentumScrollBegin}
        onScrollEndDrag={onScrollEndDrag}
        onMomentumScrollEnd={onMomentumScrollEnd}
        ItemSeparatorComponent={() => <View style={{height: 10}} />}
        ListHeaderComponent={() => <View style={{height: 10}} />}
        contentContainerStyle={{
          paddingTop: HeaderHeight + TabBarHeight,
          paddingHorizontal: 10,
          minHeight: windowHeight - SafeStatusBar + HeaderHeight,
        }}
        showsHorizontalScrollIndicator={false}
        data={data}
        renderItem={renderItem}
        showsVerticalScrollIndicator={false}
        keyExtractor={(item, index) => index.toString()}
      />
    );
  };

  const renderTabBar = (props) => {
    const y = scrollY.interpolate({
      inputRange: [0, HeaderHeight],
      outputRange: [HeaderHeight, 0],
      extrapolate: 'clamp',
    });
    return (
      <Animated.View
        style={{
          top: 0,
          zIndex: 1,
          position: 'absolute',
          transform: [{translateY: y}],
          width: '100%',
        }}>
        <TabBar
          {...props}
          onTabPress={({route, preventDefault}) => {
            if (isListGliding.current) {
              preventDefault();
            }
          }}
          style={styles.tab}
          renderLabel={renderLabel}
          indicatorStyle={styles.indicator}
        />
      </Animated.View>
    );
  };

  const renderTabView = () => {
    return (
      <TabView
        onSwipeStart={() => setCanScroll(false)}
        onSwipeEnd={() => setCanScroll(true)}
        onIndexChange={(id) => {
          _tabIndex.current = id;
          setIndex(id);
        }}
        navigationState={{index: tabIndex, routes}}
        renderScene={renderScene}
        renderTabBar={renderTabBar}
        initialLayout={{
          height: 0,
          width: windowWidth,
        }}
      />
    );
  };

  return (
    <View style={styles.container}>
      {renderTabView()}
      {renderHeader()}
    </View>
  );
}
Example #14
Source File: react-native-ring-picker.js    From react-native-ring-picker with GNU General Public License v3.0 4 votes vote down vote up
constructor(props) {
        super(props);

        let icons = this.mapPropsIconsToAnimatedOnes();

        this.state = {
            pan: new Animated.Value(0),
            icons: icons,
            showArrowHint: this.props.showArrowHint,
            currentSnappedIcon: this.getCurrentSnappedMiddleIcon(icons),
            ICON_PATH_RADIUS: 0,
            XY_AXES_COORDINATES: {
                X: 0,
                Y: 0,
                PAGE_Y: 0,
                PAGE_X: 0
            },
            CURRENT_ICON_SHIFT: 0
        };

        this.INDEX_EXTRACTORS = {};
        this.GIRTH_ANGLE = this.props.girthAngle;
        this.AMOUNT_OF_ICONS = icons.length;
        this.ICON_POSITION_ANGLE = this.GIRTH_ANGLE / this.AMOUNT_OF_ICONS;

        // 2*Ï€*r / 360
        this.STEP_LENGTH_TO_1_ANGLE = 0;

        this.DIRECTIONS = {
            CLOCKWISE: "CLOCKWISE",
            COUNTERCLOCKWISE: "COUNTERCLOCKWISE"
        };

        this.CIRCLE_SECTIONS = {
            TOP_LEFT: "TOP_LEFT",
            TOP_RIGHT: "TOP_RIGHT",
            BOTTOM_LEFT: "BOTTOM_LEFT",
            BOTTOM_RIGHT: "BOTTOM_RIGHT"
        };

        this.CURRENT_CIRCLE_SECTION = null;
        this.CURRENT_DIRECTION = null;
        this.CURRENT_VECTOR_DIFFERENCE_LENGTH = 0;

        this.PREVIOUS_POSITION = {
            X: 0,
            Y: 0
        };

        this.ICON_HIDE_ON_THE_BACK_DURATION = this.props.iconHideOnTheBackDuration;

        this.ALL_ICONS_FINISH_ANIMATIONS = {
            promises: this.state.icons.reduce((promises, icon) => {promises[icon.id] = null; return promises}, {}),
            resolvers: this.state.icons.reduce((resolvers, icon) => {resolvers[icon.id] = null; return resolvers}, {})
        };

        this._panResponder = PanResponder.create({
            onMoveShouldSetResponderCapture: () => true, //Tell iOS that we are allowing the movement
            onMoveShouldSetPanResponderCapture: () => true, // Same here, tell iOS that we allow dragging
            onPanResponderGrant: (e, gestureState) => {
                this.hideArrowHint();
                this.resetCurrentValues();
                this.setPreviousDifferenceLengths(0 ,0);
                this.state.pan.setValue(this.state.pan._value);
            },
            onPanResponderMove: (e, gestureState) => {
                this.defineCurrentSection(gestureState.moveX, gestureState.moveY);
                this.checkPreviousDifferenceLengths(gestureState.dx, gestureState.dy);

                this.state.pan.setValue(this.CURRENT_VECTOR_DIFFERENCE_LENGTH);
                this.setState({
                    ...this.state,
                    CURRENT_ICON_SHIFT: this.CURRENT_VECTOR_DIFFERENCE_LENGTH / this.STEP_LENGTH_TO_1_ANGLE
                }, () => this.calculateIconCurrentPositions(gestureState.vx));
            },
            onPanResponderRelease: (evt, gestureState) => {
                let lastGesture = {...gestureState};

                this.createFinishAnimationPromisesAndResolveIfIconsAreNotMovingAlready();

                Promise
                    .all(this.getFinishAnimationPromises())
                    .then(() => this.snapNearestIconToVerticalAxis(lastGesture));
            }
        });
    }
Example #15
Source File: index.js    From react-native-circular-picker with MIT License 4 votes vote down vote up
CircularPicker = ({
  size,
  strokeWidth,
  defaultPos,
  steps,
  gradients,
  backgroundColor,
  stepColor,
  borderColor,
  children,
  onChange,
}) => {
  const [pos, setPos] = useState(percentToPos(defaultPos));
  const circle = useRef(null);

  const padding = 8;
  const radius = (size - strokeWidth) / 2 - padding;
  const center = (radius + strokeWidth / 2);

  const gradient = selectGradient(gradients, pos);
  
  useEffect(()=>{
    setPos(percentToPos(defaultPos));
  }, [defaultPos]);

  if (steps) {
    steps = steps.map((p) => {
      const pos = percentToPos(p);
      const { x2, y2 } = calculateAngle(pos, radius);
      const { endX: x, endY: y } = calculateRealPos(x2, y2, radius, strokeWidth);
      return { x, y, p };
    });
  }

  const { x1, y1, x2, y2 } = calculateAngle(pos, radius);
  const { endX, endY } = calculateRealPos(x2, y2, radius, strokeWidth);

  const goToPercent = (p) => {
    const newPos = percentToPos(p);
    setPos(newPos);
    onChange(posToPercent(newPos));
  }

  const pan = PanResponder.create({
    onMoveShouldSetPanResponder: () => true,
    onMoveShouldSetPanResponderCapture: () => true,
    onPanResponderMove: (_, { moveX, moveY }) => {
      circle.current.measure((x, y, width, height, px, py) => {
        const newPos = calculateMovement(moveX - px, moveY - py, radius, strokeWidth);
        /**
         * @TODO
         */
        if ((newPos < -0.3 && pos > 1.3)
          || (newPos > 1.3 && pos < -0.3)) {
          return;
        }
        setPos(newPos);
        onChange(posToPercent(newPos));
      });
    }
  });

  const d = `
    M ${x2.toFixed(3)} ${y2.toFixed(3)}
    A ${radius} ${radius}
    ${(pos < 0.5) ? '1' : '0'} ${(pos > 0.5) ? '1' : '0'} 0
    ${x1.toFixed(3)} ${y1.toFixed(3)}
  `;

  return (
    <Svg height={size} width={size} ref={circle}>
      <Defs>
        <LinearGradient id="grad" x1="0" y1="0" x2="100%" y2="0">
          <Stop offset="0" stopColor={gradient[0]} />
          <Stop offset="1" stopColor={gradient[1]} />
        </LinearGradient>
      </Defs>
      <G transform={{ translate: `${strokeWidth / 2 + radius + padding}, ${strokeWidth / 2 + radius + padding}` }}>
        <Circle
          r={radius}
          strokeWidth={strokeWidth}
          fill="transparent"
          stroke={backgroundColor}
        />
        <Path
          d={d}
          strokeWidth={strokeWidth}
          stroke={`url(#grad)`}
          fill="none"
        />
      </G>
      <G transform={{ translate: `${center + padding}, ${strokeWidth / 2 + padding}` }}>
        <Circle r={(strokeWidth) / 2} fill={backgroundColor} />
      </G>
      {steps && steps.map((step, index) => (
        <G transform={{ translate: `${step.x + padding}, ${step.y + padding}` }} key={index}>
          <Circle
            r={strokeWidth}
            fill="transparent"
            strokeWidth="12"
            onPress={() => goToPercent(step.p)}
          />
          <Circle
            r={(strokeWidth / 2.5) / 2}
            fill={stepColor}
            strokeWidth="12"
            onPress={() => goToPercent(step.p)}
          />
        </G>
      ))}
      <G transform={{ translate: `${endX + padding}, ${endY + padding}` }} {...pan.panHandlers}>
        <Circle
          r={(strokeWidth) / 2 + (padding / 2)}
          fill={gradient[1]}
          stroke={borderColor}
          strokeWidth={padding / 1.5}
        />
      </G>
      {children && (
        <View style={{ height: size, alignItems: 'center', justifyContent: 'center' }}>
          <View>{children}</View>
        </View>
      )}
    </Svg>
  );
}
Example #16
Source File: SingleShapeView.js    From geometry_3d with MIT License 4 votes vote down vote up
function SingleShapeView(props) {
  const { shape, edges, points, newText, oldText, newTextGeo } = props;
  /*const [cameraHandler, setCameraHandler] = useState(null);
  const [renderer, setRenderer] = useState(null);
  const [camera, setCamera] = useState(null);
  const [scene, setScene] = useState(null);*/
  let clonePoints = points
    ? points.map((item) => {
        return {
          ...item,
          text: item.text.clone(),
        };
      })
    : [];
  useEffect(() => {
    //console.log(oldText);
    const shapePosition = shape.position;
    if (newText !== "" && oldText !== "" && newTextGeo) {
      const { scene, cameraHandler } = props.singleShapeComponents;
      for (let point of clonePoints) {
        //console.log(point.trueText);
        if (point.trueText === newText) {
          //console.log("found");
          const oldTexGeo = scene.getObjectByName(oldText);
          scene.remove(oldTexGeo);
          point.text = newTextGeo.clone();
          scene.add(point.text);
          let { x, y, z } = point.text.position;
          point.text.position.set(
            x - shapePosition.x,
            y - shapePosition.y,
            z - shapePosition.z
          );
          cameraHandler.addObjectsToTrack([point.text]);
          break;
        }
      }
    }
  }, [newText, newTextGeo, oldText]);

  const _transformEvent = (event) => {
    event.preventDefault = event.preventDefault || (() => {});
    event.stopPropagation = event.stopPropagation || (() => {});
    return event;
  };

  const handleStartShouldSetPanResponder = () => {
    return true;
  };

  // We were granted responder status! Let's update the UI
  const handlePanResponderGrant = (e, gestureState) => {
    const event = _transformEvent({ ...e, gestureState });
    const { cameraHandler } = props.singleShapeComponents;
    if (cameraHandler) cameraHandler.handlePanResponderGrant(event.nativeEvent);
  };

  // Every time the touch/mouse moves
  const handlePanResponderMove = (e, gestureState) => {
    // Keep track of how far we've moved in total (dx and dy)
    const event = _transformEvent({ ...e, gestureState });
    const { cameraHandler } = props.singleShapeComponents;
    cameraHandler.handlePanResponderMove(event.nativeEvent, gestureState);
  };

  // When the touch/mouse is lifted
  const handlePanResponderEnd = (e, gestureState) => {
    const { cameraHandler } = props.singleShapeComponents;
    const event = _transformEvent({ ...e, gestureState });
    cameraHandler.handlePanResponderEnd(event.nativeEvent);
  };
  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: handleStartShouldSetPanResponder,
    onPanResponderGrant: handlePanResponderGrant,
    onPanResponderMove: handlePanResponderMove,
    onPanResponderRelease: handlePanResponderEnd,
    onPanResponderTerminate: handlePanResponderEnd,
    onShouldBlockNativeResponder: () => false,
    onPanResponderTerminationRequest: () => true,
  });
  const fitCameraToObject = (camera, object) => {
    let offset = 4;

    const boundingBox = new THREE.Box3();

    // get bounding box of object - this will be used to setup controls and camera
    boundingBox.setFromObject(object);

    //const center = boundingBox.getCenter();

    const size = boundingBox.getSize();

    // get the max side of the bounding box (fits to width OR height as needed )
    const maxDim = Math.max(size.x, size.y, size.z);
    const fov = camera.fov * (Math.PI / 180);
    let cameraZ = Math.abs((maxDim / 4) * Math.tan(fov * 2));

    cameraZ *= offset; // zoom out a little so that objects don't fill the screen

    camera.position.z = cameraZ;

    const minZ = boundingBox.min.z;
    const cameraToFarEdge = minZ < 0 ? -minZ + cameraZ : cameraZ - minZ;

    camera.far = cameraToFarEdge * 3;
    camera.updateProjectionMatrix();
    return cameraZ;
  };

  const onContextCreate = ({ gl, width, height, scale }) => {
    console.log(points.length);
    let _renderer = new ExpoTHREE.Renderer({ gl });
    _renderer.setPixelRatio(scale);
    _renderer.setSize(width, height);
    _renderer.setClearColor(0x000000, 1.0);
    //console.log(renderer.domElement)

    let shapePosition = shape.position;
    const cloneShape = shape.clone();
    const cloneEdges = edges.clone();
    cloneShape.position.set(0, 0, 0);
    cloneEdges.position.set(0, 0, 0);
    let _scene = new THREE.Scene();
    let _camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 10000);
    const baseDistance = 10; //fitCameraToObject(_camera, cloneShape);
    let _cameraHandler = new UniCameraHandler(_camera, baseDistance);
    for (let point of clonePoints) {
      const textGeo = point.text;
      textGeo.name = point.trueText;
      let { x, y, z } = textGeo.position;
      //console.log(cloneShape.position);
      textGeo.position.set(
        x - shapePosition.x,
        y - shapePosition.y,
        z - shapePosition.z
      );
      //console.log(textGeo.position);
      _scene.add(textGeo);
    }
    const axisHelper = new THREE.AxesHelper(200);
    cloneShape.add(axisHelper);
    _scene.add(cloneShape, cloneEdges);
    props.reduxSetSingleShapeComponents({
      cameraHandler: _cameraHandler,
      camera: _camera,
      renderer: _renderer,
      scene: _scene,
    });
  };

  const onRender = () => {
    let {
      cameraHandler,
      renderer,
      camera,
      scene,
    } = props.singleShapeComponents;
    cameraHandler.render(clonePoints);
    renderer.render(scene, camera);
  };

  return (
    <View
      {...panResponder.panHandlers}
      style={{
        flex: 1,
        borderWidth: 1,
        borderRadius: 5,
      }}
    >
      <ExpoGraphics.View
        // style={{ flex: 1 }}
        onContextCreate={(props) => {
          onContextCreate(props);
        }}
        onRender={(_props) => onRender(_props)}
        arEnabled={false}
        onShouldReloadContext={() => true}
      />
    </View>
  );
}
Example #17
Source File: LayoutSetup.js    From geometry_3d with MIT License 4 votes vote down vote up
function LayoutSetup(props) {
  const raw_font = require("../../assets/fonts/bebas_neue.typeface.json");
  const font = new THREE.Font(raw_font);
  const [pan, setPan] = useState(new Animated.ValueXY());
  const [mouse, setMouse] = useState(new THREE.Vector2(-10, -10));
  const [val, setVal] = useState({ x: 0, y: 0 });
  const [isLock, setIsLock] = useState(false);
  const _transformEvent = (event) => {
    event.preventDefault = event.preventDefault || (() => {});
    event.stopPropagation = event.stopPropagation || (() => {});
    return event;
  };

  //Should we become active when the user presses down on the square?
  const handleStartShouldSetPanResponder = () => {
    return true;
  };

  // We were granted responder status! Let's update the UI
  const handlePanResponderGrant = (e, gestureState) => {
    const event = _transformEvent({ ...e, gestureState });
    const disableCamera = props.miscData.disableCamera;
    if (disableCamera) {
      pan.setOffset({
        x: val.x,
        y: val.y,
      });
      pan.setValue({ x: -10, y: -10 });
      mouse.x = (event.nativeEvent.pageX / width) * 2 - 1;
      mouse.y = -(event.nativeEvent.pageY / height) * 2 + 1;
      //props.basicComponents.controls.onDocumentTouchStart(mouse);
    }
    if (!disableCamera)
      props.basicComponents.cameraHandler.handlePanResponderGrant(
        event.nativeEvent
      );
  };

  // Every time the touch/mouse moves
  const handlePanResponderMove = (e, gestureState) => {
    // Keep track of how far we've moved in total (dx and dy)
    const event = _transformEvent({ ...e, gestureState });
    const disableCamera = props.miscData.disableCamera;
    if (disableCamera) {
      mouse.x = (event.nativeEvent.pageX / width) * 2 - 1;
      mouse.y = -(event.nativeEvent.pageY / height) * 2 + 1;
      //props.basicComponents.controls.onDocumentMouseMove(mouse);
    }
    if (!disableCamera)
      props.basicComponents.cameraHandler.handlePanResponderMove(
        event.nativeEvent,
        gestureState
      );
  };

  // When the touch/mouse is lifted
  const handlePanResponderEnd = (e, gestureState) => {
    const event = _transformEvent({ ...e, gestureState });
    //clearTimeout(longPressTimeout);
    const disableCamera = props.miscData.disableCamera;
    if (disableCamera) {
      mouse.x = -10;
      mouse.y = -10;
      //props.basicComponents.controls.onDocumentMouseCancel();
    }
    if (!disableCamera)
      props.basicComponents.cameraHandler.handlePanResponderEnd(
        event.nativeEvent
      );
  };

  pan.addListener((value) => setVal(() => value));
  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: handleStartShouldSetPanResponder,
    onPanResponderGrant: handlePanResponderGrant,
    onPanResponderMove: handlePanResponderMove,
    onPanResponderRelease: handlePanResponderEnd,
    onPanResponderTerminate: handlePanResponderEnd,
    onShouldBlockNativeResponder: () => false,
    onPanResponderTerminationRequest: () => true,
  });
  const updatePoints = () => {
    if (props.basicComponents.points != null) {
      props.getPointsCallback(
        props.basicComponents.points.map((item) => {
          return {
            point: item.position,
            text: item.trueText,
            item: item,
          };
        })
      );
    }
  }; //, [props.basicComponents.points]);
  const loadFont = (listOfVertices) => {
    let listOfObjects = [];
    for (let item of listOfVertices) {
      const vertex = item.point;
      const textGeo = new THREE.TextGeometry(item.text, {
        font: font,
        size: 0.5,
        height: 0.01,
      });

      let textMaterial = new THREE.MeshBasicMaterial({
        color: 0xffffff,
      });
      let text = new THREE.Mesh(textGeo, textMaterial);
      props.basicComponents.scene.add(text);
      text.position.set(vertex.x, vertex.y, vertex.z);
      text.quaternion.copy(props.basicComponents.camera.quaternion);

      let point = {
        text: text,
        position: vertex,
        trueText: item.text,
      };
      listOfObjects.push(point);
    }
    let holder =
      props.basicComponents.points == null ? [] : props.basicComponents.points;
    props.reduxSetPoint([...listOfObjects, ...holder]);
    updatePoints();
    return listOfObjects;
  };

  const onContextCreate = ({ gl, width, height, scale }) => {
    if (props.basicComponents.scene) props.basicComponents.scene.dispose();

    props.reduxSetPoint([]);
    props.reduxSetLine([]);
    props.reduxSetShape([]);
    props.reduxSetDisableCamera(false);
    //props.reduxSetControls(null);

    let renderer = new ExpoTHREE.Renderer({ gl });
    renderer.setPixelRatio(scale);
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 1.0);
    //console.log(renderer.domElement)

    let scene = new THREE.Scene();
    let camera = new THREE.PerspectiveCamera(100, width / height, 0.1, 1000);
    //grid size = 2pixel
    let geometry = null;
    switch (props.initShape) {
      case "cube": {
        geometry = new THREE.BoxBufferGeometry(5, 5, 5);
        //numVertices = 8
        break;
      }
      case "cone": {
        geometry = new THREE.ConeBufferGeometry(5, 10, 32);
        //numVertices = 1
        break;
      }
      case "sphere": {
        geometry = new THREE.SphereBufferGeometry(5, 20, 20);
        //numVertices = 1
        break;
      }
      case "octahedron": {
        geometry = new THREE.OctahedronBufferGeometry(5, 0);
        //numVertices = 6
        break;
      }
      case "prism": {
        geometry = new THREE.CylinderBufferGeometry(5, 5, 10, 3);
        break;
      }
      default: {
        if (props.savedState) {
          loadSavedState(props, scene, updatePoints);
        }
        break;
      }
    }

    const material = new THREE.MeshBasicMaterial({
      color: 0xe7ff37,
      opacity: 0.5,
      transparent: true,
      side: THREE.DoubleSide,
    });
    const axisHelper = new THREE.AxesHelper(200);
    const cameraHandler = new UniCameraHandler(camera);
    props.getCam(cameraHandler);
    const plane = new THREE.GridHelper(200, 200, "#ff3700");

    scene.add(plane, axisHelper);
    props.reduxSetBasicComponents({
      camera: camera,
      cameraHandler: cameraHandler,
      scene: scene,
      renderer: renderer,
    });

    if (geometry) {
      let mesh = new THREE.Mesh(geometry, material);
      let edges = new THREE.EdgesGeometry(geometry);
      let line = new THREE.LineSegments(
        edges,
        new THREE.LineBasicMaterial({ color: 0xffffff })
      );
      //let wrapper = new THREE.Object3D();
      //wrapper.add(mesh, line);
      scene.add(mesh, line);

      const holder = loadFont(getVerticesWithText(mesh, props.initShape));
      //console.log(holder)
      props.reduxSetShape([
        {
          object: mesh,
          edges: line,
          color: "#e7ff37",
          name: "default",
          type: props.initShape,
          id: 0,
          position: new THREE.Vector3(0, 0, 0),
          rotation: new THREE.Vector3(0, 0, 0),
          points: holder,
        },
      ]);
      props.getShapesCallback(props.basicComponents.shapes);
    }
  };

  const onRender = (delta) => {
    props.basicComponents.cameraHandler.render(props.basicComponents.points);
    props.basicComponents.renderer.render(
      props.basicComponents.scene,
      props.basicComponents.camera
    );
  };

  useEffect(() => {
    if (props.pointsEdit && props.pointsEdit.length > 0) {
      switch (props.action) {
        case "add_points": {
          addPoints(loadFont, props.pointsEdit);
          break;
        }
        case "remove_points": {
          removePoints(props, updatePoints, props.pointsEdit);
          break;
        }
        default: {
          break;
        }
      }
    }
  }, [props.signalEditPoints]);
  useEffect(() => {
    if (props.pointsConnect && props.pointsConnect.length > 0) {
      switch (props.action) {
        case "connect_points": {
          connectPoints(props, loadFont, props.pointsConnect);
          break;
        }
        case "disconnect_points": {
          disconnectPoints(props, props.pointsConnect);
          break;
        }
        default: {
          break;
        }
      }
    }
  }, [props.signalPoints]);

  useEffect(() => {
    if (props.shapesConnect && props.shapesConnect.length > 0) {
      switch (props.action) {
        case "add_shapes": {
          addShapes(props, props.shapesConnect, updatePoints);
          break;
        }
        case "remove_shapes": {
          removeShapes(props, props.shapesConnect);
          break;
        }
        default: {
          break;
        }
      }
    }
  }, [props.signalShapes]);

  return (
    <>
      <View
        {...panResponder.panHandlers}
        style={{
          flex: 1,
          overflow: "hidden",
          width: "100%",
          height: "100%",
        }}
      >
        <ExpoGraphics.View
          style={{ flex: 1 }}
          onContextCreate={(props) => {
            onContextCreate(props);
          }}
          onRender={(_props) => onRender(_props)}
          arEnabled={false}
          onShouldReloadContext={() => true}
        />
      </View>
      <TouchableOpacity
        style={{
          backgroundColor: "black",
          top: 20,
          right: 90,
          position: "absolute",
          paddingVertical: 5,
          paddingHorizontal: 10,
          borderRadius: 5,
          borderColor: "red",
          borderWidth: 2,
        }}
        onPress={() => {
          props.reduxSetDisableCamera(!props.miscData.disableCamera);
          setIsLock(() => !isLock);
        }}
      >
        <Text
          style={{
            color: "red",
          }}
        >
          {isLock ? "Unlock" : "Lock"}
        </Text>
      </TouchableOpacity>
      <TouchableOpacity
        style={{
          backgroundColor: "black",
          top: 20,
          right: 20,
          position: "absolute",
          paddingVertical: 5,
          paddingHorizontal: 10,
          borderRadius: 5,
          borderColor: "white",
          borderWidth: 2,
        }}
        onPress={() => {
          const currentItem = {
            shapes: props.basicComponents.shapes,
            lines: props.basicComponents.lines,
            points: props.basicComponents.points,
            name: formatDateTime(new Date()),
            fileName: Date.now(),
            isSynced: false,
            url: "",
          };
          props.reduxAddSaveItem(currentItem);
          Toast.show({
            type: "success",
            position: "top",
            text1: "Current state saved",
            text2: "Success",
            visibilityTime: 3000,
            autoHide: true,
          });
        }}
      >
        <Text
          style={{
            color: "white",
          }}
        >
          Save
        </Text>
      </TouchableOpacity>
    </>
  );
}
Example #18
Source File: index.js    From designcode-app with MIT License 4 votes vote down vote up
export default function Projects() {
  const [index, setIndex] = useState(0);
  const [pan, setPan] = useState(new Animated.ValueXY());
  const [maskOpacity, setMaskOpacity] = useState(new Animated.Value(0));
  const [scale, setScale] = useState(new Animated.Value(0.9));
  const [translateY, setTranslateY] = useState(new Animated.Value(44));
  const [thirdScale, setThirdScale] = useState(new Animated.Value(0.8));
  const [thirdTranslateY, setThirdTranslateY] = useState(
    new Animated.Value(-50)
  );

  const action = store.getState().app.action;

  const panResponder = useMemo(() => {
    return PanResponder.create({
      onMoveShouldSetPanResponder: (event, gestureState) => {
        if (gestureState.dx === 0 && gestureState.dy === 0) {
          return false;
        } else {
          if (store.getState().app.action === "openCard") {
          } else {
            return true;
          }
        }
      },
      onPanResponderGrant: () => {
        Animated.spring(scale, { toValue: 1, useNativeDriver: false }).start();
        Animated.spring(translateY, {
          toValue: 0,
          useNativeDriver: false, //true
        }).start();

        Animated.spring(thirdScale, {
          toValue: 0.9,
          useNativeDriver: false, //true
        }).start();

        Animated.spring(thirdTranslateY, {
          toValue: 44,
          useNativeDriver: false, //true
        }).start();

        Animated.timing(maskOpacity, {
          toValue: 1,
          useNativeDriver: false, //true
        }).start();
      },

      onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }], {
        useNativeDriver: false,
      }),

      onPanResponderRelease: () => {
        const positionY = pan.y.__getValue();

        Animated.timing(maskOpacity, {
          toValue: 0,
          useNativeDriver: false, //true
        }).start();

        if (positionY > 200) {
          Animated.timing(pan, {
            toValue: { x: 0, y: 1000 },
            useNativeDriver: false, //true
          }).start(() => {
            pan.setValue({ x: 0, y: 0 });
            scale.setValue(0.9);
            translateY.setValue(44);
            thirdScale.setValue(0.8);
            thirdTranslateY.setValue(-50);
            setIndex(getNextIndex(index));
          });
        } else {
          Animated.spring(pan, {
            toValue: { x: 0, y: 0 },
            useNativeDriver: false, //true
          }).start();

          Animated.spring(scale, {
            toValue: 0.9,
            useNativeDriver: false, //true
          }).start();

          Animated.spring(translateY, {
            toValue: 44,
            useNativeDriver: false, //true
          }).start();

          Animated.spring(thirdScale, {
            toValue: 0.8,
            useNativeDriver: false, //true
          }).start();

          Animated.spring(thirdTranslateY, {
            toValue: -50,
            useNativeDriver: false, //true
          }).start();
        }
      },
    });
  }, [index]);

  return (
    <Container>
      <AnimatedMask style={{ opacity: maskOpacity }} />
      <Animated.View
        style={{
          transform: [{ translateX: pan.x }, { translateY: pan.y }],
        }}
        {...panResponder.panHandlers}
      >
        <Project
          title={projects[index].title}
          image={projects[index].image}
          author={projects[index].author}
          text={projects[index].text}
          canOpen={true}
        />
      </Animated.View>
      <Animated.View
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          zIndex: -1,
          width: "100%",
          height: "100%",
          justifyContent: "center",
          alignItems: "center",
          transform: [{ scale: scale }, { translateY: translateY }],
        }}
      >
        <Project
          title={projects[getNextIndex(index)].title}
          image={projects[getNextIndex(index)].image}
          author={projects[getNextIndex(index)].author}
          text={projects[getNextIndex(index)].text}
        />
      </Animated.View>
      <Animated.View
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          zIndex: -3,
          width: "100%",
          height: "100%",
          justifyContent: "center",
          alignItems: "center",
          transform: [{ scale: thirdScale }, { translateY: thirdTranslateY }],
        }}
      >
        <Project
          title={projects[getNextIndex(index + 1)].title}
          image={projects[getNextIndex(index + 1)].image}
          author={projects[getNextIndex(index + 1)].author}
          text={projects[getNextIndex(index + 1)].text}
        />
      </Animated.View>
    </Container>
  );
}
Example #19
Source File: PullRefreshTabView.js    From react-native-collapsible-tabview with MIT License 4 votes vote down vote up
App = () => {
  /**
   * stats
   */
  const [tabIndex, setIndex] = useState(0);
  const [routes] = useState([
    {key: 'tab1', title: 'Tab1'},
    {key: 'tab2', title: 'Tab2'},
  ]);
  const [canScroll, setCanScroll] = useState(true);
  const [tab1Data] = useState(Array(40).fill(0));
  const [tab2Data] = useState(Array(30).fill(0));

  /**
   * ref
   */
  const scrollY = useRef(new Animated.Value(0)).current;
  const headerScrollY = useRef(new Animated.Value(0)).current;
  // for capturing header scroll on Android
  const headerMoveScrollY = useRef(new Animated.Value(0)).current;
  const listRefArr = useRef([]);
  const listOffset = useRef({});
  const isListGliding = useRef(false);
  const headerScrollStart = useRef(0);
  const _tabIndex = useRef(0);
  const refreshStatusRef = useRef(false);

  /**
   * PanResponder for header
   */
  const headerPanResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
      onStartShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        syncScrollOffset();
        return false;
      },

      onMoveShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        return Math.abs(gestureState.dy) > 5;
      },
      onPanResponderEnd: (evt, gestureState) => {
        handlePanReleaseOrEnd(evt, gestureState);
      },
      onPanResponderMove: (evt, gestureState) => {
        const curListRef = listRefArr.current.find(
          (ref) => ref.key === routes[_tabIndex.current].key,
        );
        const headerScrollOffset = -gestureState.dy + headerScrollStart.current;
        if (curListRef.value) {
          // scroll up
          if (headerScrollOffset > 0) {
            curListRef.value.scrollToOffset({
              offset: headerScrollOffset,
              animated: false,
            });
            // start pull down
          } else {
            if (Platform.OS === 'ios') {
              curListRef.value.scrollToOffset({
                offset: headerScrollOffset / 3,
                animated: false,
              });
            } else if (Platform.OS === 'android') {
              if (!refreshStatusRef.current) {
                headerMoveScrollY.setValue(headerScrollOffset / 1.5);
              }
            }
          }
        }
      },
      onShouldBlockNativeResponder: () => true,
      onPanResponderGrant: (evt, gestureState) => {
        headerScrollStart.current = scrollY._value;
      },
    }),
  ).current;

  /**
   * PanResponder for list in tab scene
   */
  const listPanResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
      onStartShouldSetPanResponder: (evt, gestureState) => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        headerScrollY.stopAnimation();
        return false;
      },
      onShouldBlockNativeResponder: () => true,
      onPanResponderGrant: (evt, gestureState) => {
        headerScrollY.stopAnimation();
      },
    }),
  ).current;

  /**
   * effect
   */
  useEffect(() => {
    scrollY.addListener(({value}) => {
      const curRoute = routes[tabIndex].key;
      listOffset.current[curRoute] = value;
    });

    headerScrollY.addListener(({value}) => {
      listRefArr.current.forEach((item) => {
        if (item.key !== routes[tabIndex].key) {
          return;
        }
        if (value > HeaderHeight || value < 0) {
          headerScrollY.stopAnimation();
          syncScrollOffset();
        }
        if (item.value && value <= HeaderHeight) {
          item.value.scrollToOffset({
            offset: value,
            animated: false,
          });
        }
      });
    });
    return () => {
      scrollY.removeAllListeners();
      headerScrollY.removeAllListeners();
    };
  }, [routes, tabIndex]);

  /**
   *  helper functions
   */
  const syncScrollOffset = () => {
    const curRouteKey = routes[_tabIndex.current].key;

    listRefArr.current.forEach((item) => {
      if (item.key !== curRouteKey) {
        if (scrollY._value < HeaderHeight && scrollY._value >= 0) {
          if (item.value) {
            item.value.scrollToOffset({
              offset: scrollY._value,
              animated: false,
            });
            listOffset.current[item.key] = scrollY._value;
          }
        } else if (scrollY._value >= HeaderHeight) {
          if (
            listOffset.current[item.key] < HeaderHeight ||
            listOffset.current[item.key] == null
          ) {
            if (item.value) {
              item.value.scrollToOffset({
                offset: HeaderHeight,
                animated: false,
              });
              listOffset.current[item.key] = HeaderHeight;
            }
          }
        }
      }
    });
  };

  const startRefreshAction = () => {
    if (Platform.OS === 'ios') {
      listRefArr.current.forEach((listRef) => {
        listRef.value.scrollToOffset({
          offset: -50,
          animated: true,
        });
      });
      refresh().finally(() => {
        syncScrollOffset();
        // do not bounce back if user scroll to another position
        if (scrollY._value < 0) {
          listRefArr.current.forEach((listRef) => {
            listRef.value.scrollToOffset({
              offset: 0,
              animated: true,
            });
          });
        }
      });
    } else if (Platform.OS === 'android') {
      Animated.timing(headerMoveScrollY, {
        toValue: -150,
        duration: 300,
        useNativeDriver: true,
      }).start();
      refresh().finally(() => {
        Animated.timing(headerMoveScrollY, {
          toValue: 0,
          duration: 300,
          useNativeDriver: true,
        }).start();
      });
    }
  };

  const handlePanReleaseOrEnd = (evt, gestureState) => {
    // console.log('handlePanReleaseOrEnd', scrollY._value);
    syncScrollOffset();
    headerScrollY.setValue(scrollY._value);
    if (Platform.OS === 'ios') {
      if (scrollY._value < 0) {
        if (scrollY._value < -PullToRefreshDist && !refreshStatusRef.current) {
          startRefreshAction();
        } else {
          // should bounce back
          listRefArr.current.forEach((listRef) => {
            listRef.value.scrollToOffset({
              offset: 0,
              animated: true,
            });
          });
        }
      } else {
        if (Math.abs(gestureState.vy) < 0.2) {
          return;
        }
        Animated.decay(headerScrollY, {
          velocity: -gestureState.vy,
          useNativeDriver: true,
        }).start(() => {
          syncScrollOffset();
        });
      }
    } else if (Platform.OS === 'android') {
      if (
        headerMoveScrollY._value < 0 &&
        headerMoveScrollY._value / 1.5 < -PullToRefreshDist
      ) {
        startRefreshAction();
      } else {
        Animated.timing(headerMoveScrollY, {
          toValue: 0,
          duration: 300,
          useNativeDriver: true,
        }).start();
      }
    }
  };

  const onMomentumScrollBegin = () => {
    isListGliding.current = true;
  };

  const onMomentumScrollEnd = () => {
    isListGliding.current = false;
    syncScrollOffset();
    // console.log('onMomentumScrollEnd'); 
  };

  const onScrollEndDrag = (e) => {
    syncScrollOffset();

    const offsetY = e.nativeEvent.contentOffset.y;
    // console.log('onScrollEndDrag', offsetY);
    // iOS only
    if (Platform.OS === 'ios') {
      if (offsetY < -PullToRefreshDist && !refreshStatusRef.current) {
        startRefreshAction();
      }
    }

    // check pull to refresh
  };

  const refresh = async () => {
    console.log('-- start refresh');
    refreshStatusRef.current = true;
    await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('done');
      }, 2000);
    }).then((value) => {
      console.log('-- refresh done!');
      refreshStatusRef.current = false;
    });
  };

  /**
   * render Helper
   */
  const renderHeader = () => {
    const y = scrollY.interpolate({
      inputRange: [0, HeaderHeight],
      outputRange: [0, -HeaderHeight],
      extrapolateRight: 'clamp',
      // extrapolate: 'clamp',
    });
    return (
      <Animated.View
        {...headerPanResponder.panHandlers}
        style={[styles.header, {transform: [{translateY: y}]}]}>
        <TouchableOpacity
          style={{flex: 1, justifyContent: 'center'}}
          activeOpacity={1}
          onPress={() => Alert.alert('header Clicked!')}>
          <Text>Pull to Refresh Header</Text>
        </TouchableOpacity>
      </Animated.View>
    );
  };

  const rednerTab1Item = ({item, index}) => {
    return (
      <View
        style={{
          borderRadius: 16,
          marginLeft: index % 2 === 0 ? 0 : 10,
          width: tab1ItemSize,
          height: tab1ItemSize,
          backgroundColor: '#aaa',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <Text>{index}</Text>
      </View>
    );
  };

  const rednerTab2Item = ({item, index}) => {
    return (
      <View
        style={{
          marginLeft: index % 3 === 0 ? 0 : 10,
          borderRadius: 16,
          width: tab2ItemSize,
          height: tab2ItemSize,
          backgroundColor: '#aaa',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <Text>{index}</Text>
      </View>
    );
  };

  const renderLabel = ({route, focused}) => {
    return (
      <Text style={[styles.label, {opacity: focused ? 1 : 0.5}]}>
        {route.title}
      </Text>
    );
  };

  const renderScene = ({route}) => {
    const focused = route.key === routes[tabIndex].key;
    let numCols;
    let data;
    let renderItem;
    switch (route.key) {
      case 'tab1':
        numCols = 2;
        data = tab1Data;
        renderItem = rednerTab1Item;
        break;
      case 'tab2':
        numCols = 3;
        data = tab2Data;
        renderItem = rednerTab2Item;
        break;
      default:
        return null;
    }
    return (
      <Animated.FlatList
        scrollToOverflowEnabled={true}
        // scrollEnabled={canScroll}
        {...listPanResponder.panHandlers}
        numColumns={numCols}
        ref={(ref) => {
          if (ref) {
            const found = listRefArr.current.find((e) => e.key === route.key);
            if (!found) {
              listRefArr.current.push({
                key: route.key,
                value: ref,
              });
            }
          }
        }}
        scrollEventThrottle={16}
        onScroll={
          focused
            ? Animated.event(
                [
                  {
                    nativeEvent: {contentOffset: {y: scrollY}},
                  },
                ],
                {useNativeDriver: true},
              )
            : null
        }
        onMomentumScrollBegin={onMomentumScrollBegin}
        onScrollEndDrag={onScrollEndDrag}
        onMomentumScrollEnd={onMomentumScrollEnd}
        ItemSeparatorComponent={() => <View style={{height: 10}} />}
        ListHeaderComponent={() => <View style={{height: 10}} />}
        contentContainerStyle={{
          paddingTop: HeaderHeight + TabBarHeight,
          paddingHorizontal: 10,
          minHeight: windowHeight - SafeStatusBar + HeaderHeight,
        }}
        showsHorizontalScrollIndicator={false}
        data={data}
        renderItem={renderItem}
        showsVerticalScrollIndicator={false}
        keyExtractor={(item, index) => index.toString()}
      />
    );
  };

  const renderTabBar = (props) => {
    const y = scrollY.interpolate({
      inputRange: [0, HeaderHeight],
      outputRange: [HeaderHeight, 0],
      // extrapolate: 'clamp',
      extrapolateRight: 'clamp',
    });
    return (
      <Animated.View
        style={{
          top: 0,
          zIndex: 1,
          position: 'absolute',
          transform: [{translateY: y}],
          width: '100%',
        }}>
        <TabBar
          {...props}
          onTabPress={({route, preventDefault}) => {
            if (isListGliding.current) {
              preventDefault();
            }
          }}
          style={styles.tab}
          renderLabel={renderLabel}
          indicatorStyle={styles.indicator}
        />
      </Animated.View>
    );
  };

  const renderTabView = () => {
    return (
      <TabView
        onSwipeStart={() => setCanScroll(false)}
        onSwipeEnd={() => setCanScroll(true)}
        onIndexChange={(id) => {
          _tabIndex.current = id;
          setIndex(id);
        }}
        navigationState={{index: tabIndex, routes}}
        renderScene={renderScene}
        renderTabBar={renderTabBar}
        initialLayout={{
          height: 0,
          width: windowWidth,
        }}
      />
    );
  };

  const renderCustomRefresh = () => {
    // headerMoveScrollY
    return Platform.select({
      ios: (
        <AnimatedIndicator
          style={{
            top: -50,
            position: 'absolute',
            alignSelf: 'center',
            transform: [
              {
                translateY: scrollY.interpolate({
                  inputRange: [-100, 0],
                  outputRange: [120, 0],
                  extrapolate: 'clamp',
                }),
              },
            ],
          }}
          animating
        />
      ),
      android: (
        <Animated.View
          style={{
            transform: [
              {
                translateY: headerMoveScrollY.interpolate({
                  inputRange: [-300, 0],
                  outputRange: [150, 0],
                  extrapolate: 'clamp',
                }),
              },
            ],
            backgroundColor: '#eee',
            height: 38,
            width: 38,
            borderRadius: 19,
            borderWidth: 2,
            borderColor: '#ddd',
            justifyContent: 'center',
            alignItems: 'center',
            alignSelf: 'center',
            top: -50,
            position: 'absolute',
          }}>
          <ActivityIndicator animating />
        </Animated.View>
      ),
    });
  };

  return (
    <View style={styles.container}>
      {renderTabView()}
      {renderHeader()}
      {renderCustomRefresh()}
    </View>
  );
}