react-native-reanimated#Extrapolate TypeScript Examples

The following examples show how to use react-native-reanimated#Extrapolate. 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.ts    From react-native-wagmi-charts with MIT License 7 votes vote down vote up
export function getY({
  value,
  domain,
  maxHeight,
}: {
  value: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  return interpolate(value, domain, [maxHeight, 0], Extrapolate.CLAMP);
}
Example #2
Source File: utils.ts    From react-native-wagmi-charts with MIT License 7 votes vote down vote up
export function getHeight({
  value,
  domain,
  maxHeight,
}: {
  value: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  return interpolate(
    value,
    [0, Math.max(...domain) - Math.min(...domain)],
    [0, maxHeight],
    Extrapolate.CLAMP
  );
}
Example #3
Source File: utils.ts    From react-native-wagmi-charts with MIT License 7 votes vote down vote up
export function getPrice({
  y,
  domain,
  maxHeight,
}: {
  y: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  if (y === -1) return -1;
  return interpolate(y, [0, maxHeight], domain.reverse(), Extrapolate.CLAMP);
}
Example #4
Source File: getHeight.ts    From react-native-wagmi-charts with MIT License 6 votes vote down vote up
export function getHeight({
  value,
  domain,
  maxHeight,
}: {
  value: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  return interpolate(
    value,
    [0, Math.max(...domain) - Math.min(...domain)],
    [0, maxHeight],
    Extrapolate.CLAMP
  );
}
Example #5
Source File: getPrice.ts    From react-native-wagmi-charts with MIT License 6 votes vote down vote up
export function getPrice({
  y,
  domain,
  maxHeight,
}: {
  y: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  if (y === -1) return -1;
  return interpolate(y, [0, maxHeight], domain.reverse(), Extrapolate.CLAMP);
}
Example #6
Source File: getY.ts    From react-native-wagmi-charts with MIT License 6 votes vote down vote up
export function getY({
  value,
  domain,
  maxHeight,
}: {
  value: number;
  domain: TDomain;
  maxHeight: number;
}) {
  'worklet';
  return interpolate(value, domain, [maxHeight, 0], Extrapolate.CLAMP);
}
Example #7
Source File: ScalableImageExample.tsx    From react-native-gallery-toolkit with MIT License 5 votes vote down vote up
export default function StandaloneGalleryBasicScreen() {
  const { controlsStyles, setControlsHidden } = useControls();

  const opacity = useSharedValue(0);

  const overlayStyles = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
      backgroundColor: 'black',
    };
  });

  const onScale = useWorkletCallback((scale: number) => {
    opacity.value = interpolate(
      scale,
      [1, 2],
      [0, 0.3],
      Extrapolate.CLAMP,
    );
  }, []);

  const onGestureStartCallback = () => {
    StatusBar.setHidden(true);
  };
  const onGestureReleaseCallback = () => {
    StatusBar.setHidden(false);
  };

  const onGestureStart = useWorkletCallback(() => {
    setControlsHidden(true);
    runOnJS(onGestureStartCallback)();
  });

  const onGestureRelease = useWorkletCallback(() => {
    setControlsHidden(false);
    runOnJS(onGestureReleaseCallback)();
  });

  return (
    <View style={{ flex: 1, backgroundColor: 'transparent' }}>
      <Animated.View
        pointerEvents="none"
        style={[StyleSheet.absoluteFill, overlayStyles]}
      />

      <View
        style={{
          zIndex: 0,
          flex: 1,
          justifyContent: 'center',
        }}
      >
        <ScalableImage
          width={image.width}
          height={image.height}
          source={image.uri}
          onScale={onScale}
          onGestureStart={onGestureStart}
          onGestureRelease={onGestureRelease}
        />
      </View>

      <Animated.View style={controlsStyles}>
        <DetachedHeader.Container>
          <DetachedHeader />
        </DetachedHeader.Container>
      </Animated.View>
    </View>
  );
}
Example #8
Source File: HorizontalFlatListExample.tsx    From react-native-scroll-bottom-sheet with MIT License 5 votes vote down vote up
HorizontalFlatListExample: React.FC<Props> = ({ navigation }) => {
  const bottomSheetRef = React.useRef<ScrollBottomSheet<any> | null>(null);

  const animatedPosition = React.useRef(new Value(0));
  const opacity = interpolate(animatedPosition.current, {
    inputRange: [0, 1],
    outputRange: [0, 0.75],
    extrapolate: Extrapolate.CLAMP,
  });

  const renderRow = React.useCallback(
    ({ index }) => <Carousel index={index} />,
    []
  );

  return (
    <View style={styles.container}>
      <MapView
        style={StyleSheet.absoluteFillObject}
        initialRegion={initialRegion}
      />
      <Animated.View
        pointerEvents="box-none"
        style={[
          StyleSheet.absoluteFillObject,
          { backgroundColor: 'black', opacity },
        ]}
      />
      <View style={StyleSheet.absoluteFillObject} pointerEvents="box-none">
        <TouchableRipple
          style={[styles.iconContainer, { right: 16 }]}
          onPress={() => {
            bottomSheetRef.current?.snapTo(2);
          }}
          borderless
        >
          <MaterialCommunityIcons
            name="close"
            size={32}
            color="white"
            style={styles.icon}
          />
        </TouchableRipple>
        {Platform.OS === 'ios' && (
          <TouchableRipple
            style={[styles.iconContainer, { left: 16 }]}
            onPress={() => {
              navigation.goBack();
            }}
            borderless
          >
            <Ionicons
              name="ios-arrow-back"
              size={32}
              color="white"
              style={styles.icon}
            />
          </TouchableRipple>
        )}
      </View>
      <ScrollBottomSheet<string>
        ref={bottomSheetRef}
        componentType="FlatList"
        topInset={24}
        animatedPosition={animatedPosition.current}
        snapPoints={snapPointsFromTop}
        initialSnapIndex={2}
        renderHandle={() => <Handle />}
        keyExtractor={i => `row-${i}`}
        initialNumToRender={5}
        contentContainerStyle={styles.contentContainerStyle}
        data={Array.from({ length: 100 }).map((_, i) => String(i))}
        renderItem={renderRow}
      />
    </View>
  );
}
Example #9
Source File: BasicSticky.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
BasicSticky = ({
  x,
  threshold,
  itemWidth,
  itemHeight,
  stickyItemWidth,
  stickyItemHeight,
  separatorSize,
  isRTL,
}: StickyItemContentProps) => {
  //#region plus
  const animatedPlusScale = interpolate(x, {
    inputRange: [0, threshold],
    outputRange: [0, 1],
    extrapolate: Extrapolate.CLAMP,
  });
  const plusStyle = [
    styles.plus,
    {
      width: stickyItemWidth,
      height: stickyItemHeight,
      [isRTL ? 'right' : 'left']: '50%',
      transform: transformOrigin(
        { x: 0, y: 0 },
        {
          translateX: interpolate(x, {
            inputRange: [separatorSize, threshold],
            outputRange: [
              (stickyItemWidth / 2) * (isRTL ? 1 : -1),
              (itemWidth / 2 - stickyItemWidth) * (isRTL ? -1 : 1),
            ],
            extrapolate: Extrapolate.CLAMP,
          }),
          translateY: itemHeight / 2 - stickyItemHeight / 2,
          scale: animatedPlusScale,
        }
      ) as Animated.AnimatedTransform,
    },
  ];
  //#endregion

  //#region text
  const animatedTextOpacity = interpolate(x, {
    inputRange: [0, threshold * 0.6],
    outputRange: [1, 0],
    extrapolate: Extrapolate.CLAMP,
  });
  const textStyle = [
    styles.text,
    {
      opacity: animatedTextOpacity,
      paddingHorizontal: separatorSize,
      lineHeight: itemHeight,
      transform: [
        {
          translateY: 0,
        },
      ] as Animated.AnimatedTransform,
    },
  ];
  //#endregion

  return (
    <>
      <Animated.View style={plusStyle}>
        <PlusSVG />
      </Animated.View>
      <Animated.Text style={textStyle}>
        {isRTL ? 'להוסיף' : 'Add'}
      </Animated.Text>
    </>
  );
}
Example #10
Source File: PageContent.tsx    From react-native-paper-onboarding with MIT License 5 votes vote down vote up
PageContentComponent = ({
  animatedFocus,
  image,
  title,
  description,
  titleStyle: titleStyleOverride,
  descriptionStyle: descriptionStyleOverride,
}: PageContentProps) => {
  //#region
  const animatedImageTopPosition = interpolate(animatedFocus, {
    inputRange: [0, 1],
    outputRange: [SCREEN_HEIGHT / 8, 0],
    extrapolate: Extrapolate.CLAMP,
  });
  //#endregion

  //#region styles
  const titleStyle = useMemo(
    () => [styles.title, titleStyleOverride],
    [titleStyleOverride]
  );

  const descriptionStyle = useMemo(
    () => [styles.description, descriptionStyleOverride],
    [descriptionStyleOverride]
  );

  const imageContainerStyle: any = useMemo(
    () => [
      styles.imageContainer,
      {
        transform: [{ translateY: animatedImageTopPosition }],
      },
    ],
    [animatedImageTopPosition]
  );
  //#endregion
  return (
    <>
      {image && (
        <Animated.View style={imageContainerStyle}>
          {typeof image === 'function' ? image() : image}
        </Animated.View>
      )}
      <Text style={titleStyle}>{title}</Text>
      <Text style={descriptionStyle}>{description}</Text>
    </>
  );
}
Example #11
Source File: BackgroundCircle.tsx    From react-native-paper-onboarding with MIT License 5 votes vote down vote up
BackgroundCircleComponent = ({
  index,
  animatedIndex,
  color,
  extendedSize,
  bottomPosition,
  screenDimensions,
  indicatorSize,
  animatedIndicatorsContainerPosition,
}: BackgroundCircleProps) => {
  //#region variables
  //#endregion

  //#region animations
  const animatedFocus = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 1, index, index + 1],
        outputRange: [0, 1, 2],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index]
  );
  const animatedRadius = useMemo(
    () =>
      interpolate(animatedFocus, {
        inputRange: [0, 1],
        outputRange: [0, extendedSize],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedFocus, extendedSize]
  );
  const animatedLeftPosition = useMemo(
    () =>
      add(
        animatedIndicatorsContainerPosition,
        indicatorSize / 2,
        I18nManager.isRTL
          ? -((index + 1) * indicatorSize)
          : index * indicatorSize,
        I18nManager.isRTL ? screenDimensions.width : 0
      ),
    [
      animatedIndicatorsContainerPosition,
      index,
      indicatorSize,
      screenDimensions.width,
    ]
  );
  //#endregion

  // render
  return (
    <AnimatedCircle
      r={animatedRadius}
      cy={bottomPosition}
      cx={animatedLeftPosition}
      fill={color}
    />
  );
}
Example #12
Source File: CustomView.tsx    From react-native-paper-onboarding with MIT License 5 votes vote down vote up
CustomView = ({ animatedFocus }: PageContentProps) => {
  //#region animations
  const animatedBoxTranslateY = interpolate(animatedFocus, {
    inputRange: [0, 1],
    outputRange: [100, 0],
    extrapolate: Extrapolate.CLAMP,
  });
  //#endregion

  //#region styles
  const boxStyle = [
    styles.box,
    {
      transform: [
        { translateY: animatedBoxTranslateY },
      ] as Animated.AnimatedTransform,
    },
  ];
  const item1Style = [
    styles.item,
    {
      opacity: interpolate(animatedFocus, {
        inputRange: [0.5, 1],
        outputRange: [0, 1],
        extrapolate: Extrapolate.CLAMP,
      }),
    },
  ];
  const item2Style = [
    styles.item,
    {
      opacity: interpolate(animatedFocus, {
        inputRange: [0.625, 1],
        outputRange: [0, 1],
        extrapolate: Extrapolate.CLAMP,
      }),
    },
  ];
  const item3Style = [
    styles.item2,
    {
      opacity: interpolate(animatedFocus, {
        inputRange: [0.75, 1],
        outputRange: [0, 1],
        extrapolate: Extrapolate.CLAMP,
      }),
    },
  ];
  const buttonStyle = {
    opacity: interpolate(animatedFocus, {
      inputRange: [0.875, 1],
      outputRange: [0, 1],
      extrapolate: Extrapolate.CLAMP,
    }),
  };
  //#endregion

  const handleButtonPress = () => {
    Alert.alert('Button Clicked !');
  };
  return (
    <View
      style={styles.container}
      shouldRasterizeIOS={true}
      needsOffscreenAlphaCompositing={true}
    >
      <Animated.View style={boxStyle} />
      <Animated.View style={item1Style} />
      <Animated.View style={item2Style} />
      <Animated.View style={item3Style} />
      <AnimatedTouchableOpacity style={buttonStyle} onPress={handleButtonPress}>
        <Text style={styles.buttonText}>Button</Text>
      </AnimatedTouchableOpacity>
    </View>
  );
}
Example #13
Source File: Page.tsx    From react-native-paper-onboarding with MIT License 4 votes vote down vote up
PageComponent = ({
  index,
  item,
  animatedIndex,
  indicatorSize,
  titleStyle: titleStyleOverride,
  descriptionStyle: descriptionStyleOverride,
  screenDimensions,
  safeInsets,
  handleRef,
}: PageProps) => {
  //#region animation
  const animatedFocus = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 1, index, index + 1],
        outputRange: [0, 1, 2],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index]
  );
  const animatedContentOpacity = useMemo(
    () =>
      interpolate(animatedFocus, {
        inputRange: [0.5, 1, 1.5],
        outputRange: [0, 1, 0],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedFocus]
  );

  const animatedContentTopPosition = useMemo(
    () =>
      interpolate(animatedFocus, {
        inputRange: [0, 1, 2],
        outputRange: [
          screenDimensions.height / 8,
          0,
          (screenDimensions.height / 6) * -1,
        ],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedFocus, screenDimensions.height]
  );
  //#endregion

  //#region styles
  const contentContainerStyle: any = useMemo(
    () => [
      styles.contentContainer,
      {
        marginTop: safeInsets.top,
        marginRight: safeInsets.right,
        marginLeft: safeInsets.left,
        marginBottom: safeInsets.bottom + indicatorSize + safeInsets.bottom,
        opacity: animatedContentOpacity,
        transform: [{ translateY: animatedContentTopPosition }],
      },
    ],
    [
      animatedContentOpacity,
      animatedContentTopPosition,
      safeInsets,
      indicatorSize,
    ]
  );
  const titleStyle = useMemo(
    () => [titleStyleOverride, item.titleStyle ? item.titleStyle : null],
    [item, titleStyleOverride]
  );
  const descriptionStyle = useMemo(
    () => [
      descriptionStyleOverride,
      item.descriptionStyle ? item.descriptionStyle : null,
    ],
    [item, descriptionStyleOverride]
  );
  //#endregion

  //#region memo
  const pageContentProps = useMemo(
    () => ({
      index,
      animatedFocus,
      image: item.image,
      title: item.title,
      description: item.description,
      titleStyle,
      descriptionStyle,
    }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [index, item, titleStyle, descriptionStyle]
  );
  //#endregion

  //#region callbacks
  const handleContainerRef = useCallback(
    ref => handleRef(ref, index),
    [index, handleRef]
  );
  //#endregion

  // render
  const renderContent = useCallback(() => {
    const ContentComponent: any = item.content;
    return ContentComponent ? (
      typeof ContentComponent === 'function' ? (
        ContentComponent(pageContentProps)
      ) : (
        <ContentComponent {...pageContentProps} />
      )
    ) : (
      <PageContent {...pageContentProps} />
    );
  }, [item, pageContentProps]);
  return (
    <Animated.View
      pointerEvents={index === 0 ? 'auto' : 'none'}
      ref={handleContainerRef}
      style={styles.container}
    >
      <Animated.View style={contentContainerStyle}>
        {renderContent()}
      </Animated.View>
    </Animated.View>
  );
}
Example #14
Source File: Indicator.tsx    From react-native-paper-onboarding with MIT License 4 votes vote down vote up
IndicatorComponent = ({
  index,
  indicatorSize,
  indicatorBackgroundColor,
  indicatorBorderColor,
  animatedIndex,
  item,
}: IndicatorProps) => {
  const radius = useMemo(() => (indicatorSize - 2) / 2, [indicatorSize]);

  //#region animation
  const animatedRadius = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 1, index, index + 1],
        outputRange: [radius * 0.33, radius, radius * 0.33],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index, radius]
  );

  const animatedIconScale = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 1, index, index + 1],
        outputRange: [1 * 0.33, 1, 1 * 0.33],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index]
  );

  const animatedIconOpacity = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 0.25, index, index + 0.25],
        outputRange: [0, 1, 0],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index]
  );

  const animatedCircleFillOpacity = useMemo(
    () =>
      interpolate(animatedIndex, {
        inputRange: [index - 1, index],
        outputRange: [0, 1],
        extrapolate: Extrapolate.CLAMP,
      }),
    [animatedIndex, index]
  );
  //#endregion

  //#region styles
  const containerStyle = useMemo(
    () => ({
      ...styles.container,
      ...{
        width: indicatorSize,
        height: indicatorSize,
      },
    }),
    [indicatorSize]
  );

  const iconStyle: any = useMemo(
    () => ({
      ...styles.iconContainer,
      ...{
        left: BORDER_WIDTH * 2,
        right: BORDER_WIDTH * 2,
        top: BORDER_WIDTH * 2,
        bottom: BORDER_WIDTH * 2,
        borderRadius: indicatorSize,
        opacity: animatedIconOpacity,
        transform: [{ scale: animatedIconScale }],
      },
    }),
    [animatedIconOpacity, animatedIconScale, indicatorSize]
  );
  //#endregion

  // renders
  const renderIcon = useCallback(() => {
    if (item.icon) {
      const IconComponent: any = item.icon;
      return (
        <Animated.View style={iconStyle}>
          {typeof IconComponent === 'function' ? (
            IconComponent({
              size: indicatorSize / 2,
            })
          ) : (
            <IconComponent size={indicatorSize / 2} />
          )}
        </Animated.View>
      );
    }
    return null;
  }, [item, indicatorSize, iconStyle]);

  return (
    <Animated.View style={containerStyle}>
      <Svg
        width={indicatorSize}
        height={indicatorSize}
        viewBox={`0 0 ${indicatorSize} ${indicatorSize}`}
      >
        <AnimatedCircle
          r={animatedRadius}
          cx={indicatorSize / 2}
          cy={indicatorSize / 2}
          // @ts-ignore
          fill={indicatorBackgroundColor}
          fillOpacity={animatedCircleFillOpacity}
          stroke={indicatorBorderColor}
          strokeWidth={BORDER_WIDTH}
        />
      </Svg>
      {renderIcon()}
    </Animated.View>
  );
}
Example #15
Source File: FacebookStickyStory.tsx    From react-native-sticky-item with MIT License 4 votes vote down vote up
FacebookStickyStory = ({
  x,
  threshold,
  itemWidth,
  itemHeight,
  stickyItemWidth,
  separatorSize,
  borderRadius,
  isRTL,
  theme = 'light',
}: StickyItemContentProps & FacebookStickyStoryProps) => {
  const stickyItemX = itemWidth / 2 + (itemWidth / 2 - stickyItemWidth);
  const stickyItemY = itemHeight / 2 - stickyItemWidth / 2;
  const stickyItemWidthWithoutPadding = stickyItemWidth - separatorSize * 2;
  const separatorSizeToStickyWidthScale = Math.min(
    separatorSize / stickyItemWidth,
    0.2
  );

  //#region thumbnail
  const thumbnailWidth = itemWidth;
  const thumbnailHeight = itemWidth;

  const thumbnailTranslateX =
    Math.abs(thumbnailWidth / 2 - (stickyItemX + stickyItemWidth / 2)) *
    (isRTL ? -1 : 1);
  const thumbnailTranslateY = Math.abs(
    thumbnailHeight / 2 - (stickyItemY + stickyItemWidth / 2)
  );

  const thumbnailScale =
    stickyItemWidth / itemWidth - separatorSizeToStickyWidthScale;
  const animatedThumbnailScale = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [1, thumbnailScale],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedThumbnailTranslateX = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [0, thumbnailTranslateX],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedThumbnailTranslateY = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [0, thumbnailTranslateY],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedThumbnailBorderRadius = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [
      borderRadius,
      stickyItemWidth * (separatorSizeToStickyWidthScale + 1),
    ],
    extrapolate: Extrapolate.CLAMP,
  });

  const thumbnailStyle = [
    {
      backgroundColor: theme === 'light' ? 'black' : 'white',
      width: thumbnailWidth,
      height: thumbnailHeight,
      borderRadius: animatedThumbnailBorderRadius,
      transform: [
        { translateX: (thumbnailWidth / 2) * -1 },
        { translateY: (thumbnailHeight / 2) * -1 },
        { translateX: animatedThumbnailTranslateX },
        { translateY: animatedThumbnailTranslateY },
        { translateX: thumbnailWidth / 2 },
        { translateY: thumbnailHeight / 2 },
        { scale: animatedThumbnailScale },
      ],
    },
  ];
  //#endregion

  //#region add icon
  const addIconWidth = 30;
  const addIconHeight = 30;

  const addIconPosition = findPointOnCircle({
    radius: stickyItemWidthWithoutPadding / 2,
    degrees: isRTL ? 135 : 45,
  });
  const animatedAddIconTranslateX = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [0, addIconPosition.x],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedAddIconTranslateY = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [thumbnailHeight / 2, addIconPosition.y],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedAddIconScale = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [1, 0.33],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedAddIconBorderWidth = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [3, 2],
    extrapolate: Extrapolate.CLAMP,
  });
  const addIconStyle = [
    styles.addIcon,
    {
      width: addIconWidth,
      height: addIconHeight,
      borderRadius: addIconWidth,
      borderWidth: animatedAddIconBorderWidth,
      transform: [
        { translateX: (addIconWidth / 2) * -1 },
        { translateY: (addIconHeight / 2) * -1 },
        { translateX: thumbnailWidth / 2 },
        { translateY: thumbnailHeight / 2 },
        { translateX: animatedThumbnailTranslateX },
        { translateY: animatedThumbnailTranslateY },
        { translateX: animatedAddIconTranslateX },
        { translateY: animatedAddIconTranslateY },
        { scale: animatedAddIconScale },
      ],
    },
  ];
  //#endregion

  //#region text
  const animatedTextOpacity = interpolate(x, {
    inputRange: [separatorSize, threshold * 0.6],
    outputRange: [1, 0],
    extrapolate: Extrapolate.CLAMP,
  });
  const animatedTextTranslateY = interpolate(x, {
    inputRange: [separatorSize, threshold * 0.6],
    outputRange: [itemHeight / 2 + itemHeight / 4, itemHeight / 2],
    extrapolate: Extrapolate.CLAMP,
  });
  const textStyle = [
    styles.text,
    {
      color: theme === 'light' ? 'black' : 'white',
      opacity: animatedTextOpacity,
      paddingHorizontal: separatorSize * 2,
      transform: [
        {
          translateY: animatedTextTranslateY,
        },
      ] as Animated.AnimatedTransform,
    },
  ];
  //#endregion

  return (
    <>
      <Animated.View style={thumbnailStyle} />
      <Animated.Text style={textStyle}>
        {isRTL ? `إضافة إلى قصتك` : `Create a story`}
      </Animated.Text>
      <Animated.View style={addIconStyle} />
    </>
  );
}
Example #16
Source File: StickyItem.tsx    From react-native-sticky-item with MIT License 4 votes vote down vote up
StickyItem = ({
  x,
  tapState,
  itemWidth,
  itemHeight,
  separatorSize,
  borderRadius,
  stickyItemActiveOpacity,
  stickyItemWidth,
  stickyItemHeight,
  stickyItemContent: StickyItemContent,
  stickyItemBackgroundColors,
  isRTL,
}: StickyItemProps) => {
  const threshold = itemWidth - stickyItemWidth + separatorSize;

  //#region animations
  const animatedTranslateX = multiply(
    cond(
      greaterThan(x, threshold),
      isRTL ? SCREEN_WIDTH - stickyItemWidth : itemWidth - stickyItemWidth,
      isRTL
        ? add(x, SCREEN_WIDTH - itemWidth - separatorSize)
        : sub(x, separatorSize)
    ),
    isRTL ? 1 : -1
  );
  const animatedOpacity = withTimingTransition(eq(tapState, 2), {
    duration: 125,
    easing: Easing.inOut(Easing.quad),
  });
  //#endregion

  //#region styles
  const containerStyle = useMemo(
    () => [
      styles.container,
      {
        width: itemWidth,
        height: itemHeight,
        opacity: interpolate(animatedOpacity, {
          inputRange: [0, 1],
          outputRange: [1, stickyItemActiveOpacity],
          extrapolate: Extrapolate.CLAMP,
        }),
        transform: transformOrigin(
          { x: itemWidth / 2, y: 0 },
          {
            translateX: animatedTranslateX,
          }
        ) as Animated.AnimatedTransform,
      },
    ],
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [itemWidth, itemHeight, stickyItemActiveOpacity]
  );
  const pressableViewStyle = useMemo<ViewStyle>(
    () => ({
      ...styles.pressable,
      width: stickyItemWidth,
      height: stickyItemHeight,
      borderRadius: stickyItemWidth,
      [isRTL ? 'left' : 'right']: 0,
      top: (itemHeight - stickyItemHeight) / 2,
    }),
    [isRTL, stickyItemWidth, stickyItemHeight, itemHeight]
  );
  //#endregion

  // render
  const renderContent = () => {
    const props = {
      x,
      itemWidth,
      itemHeight,
      stickyItemWidth,
      stickyItemHeight,
      separatorSize,
      borderRadius,
      threshold,
      isRTL,
    };
    return typeof StickyItemContent === 'function' ? (
      // @ts-ignore
      StickyItemContent(props)
    ) : (
      // @ts-ignore
      <StickyItemContent {...props} />
    );
  };

  return (
    <Animated.View pointerEvents="box-none" style={containerStyle}>
      <View style={pressableViewStyle} />
      <StickyItemBackground
        threshold={threshold}
        x={x}
        itemWidth={itemWidth}
        itemHeight={itemHeight}
        separatorSize={separatorSize}
        borderRadius={borderRadius}
        stickyItemWidth={stickyItemWidth}
        stickyItemHeight={stickyItemHeight}
        stickyItemBackgroundColors={stickyItemBackgroundColors}
        isRTL={isRTL}
      />
      {renderContent()}
    </Animated.View>
  );
}
Example #17
Source File: StickyItemBackground.tsx    From react-native-sticky-item with MIT License 4 votes vote down vote up
StickyItemBackground = ({
  x,
  threshold,
  itemWidth,
  itemHeight,
  separatorSize,
  borderRadius,
  stickyItemWidth,
  stickyItemHeight,
  stickyItemBackgroundColors,
  isRTL,
}: StickyItemBackgroundProps) => {
  const adjustedBorderRadius = borderRadius === 0 ? 0.0001 : borderRadius;
  const paths = useMemo(
    () => [
      generatePathData({
        x: 0,
        y: 0,
        width: itemWidth,
        height: itemHeight,
        tl: adjustedBorderRadius,
        bl: adjustedBorderRadius,
        tr: adjustedBorderRadius,
        br: adjustedBorderRadius,
      }),
      generatePathData({
        x: isRTL ? 0 : itemWidth - stickyItemWidth,
        y: itemHeight / 2 - stickyItemHeight / 2,
        width: stickyItemWidth,
        height: stickyItemHeight,
        tl: isRTL ? stickyItemHeight / 2 : 0.0001,
        bl: isRTL ? stickyItemHeight / 2 : 0.0001,
        tr: isRTL ? 0.0001 : stickyItemHeight / 2,
        br: isRTL ? 0.0001 : stickyItemHeight / 2,
      }),
    ],
    [
      adjustedBorderRadius,
      itemWidth,
      itemHeight,
      stickyItemWidth,
      stickyItemHeight,
      isRTL,
    ]
  );

  //#region animations
  const animatedTransform = transformOrigin(
    {
      x: 0,
      y: 0,
    },
    {
      translateX: 0,
    }
  );

  const animatedPathData = interpolatePath(x, {
    inputRange: [separatorSize, threshold],
    outputRange: paths,
    extrapolate: Extrapolate.CLAMP,
  });

  const animatedBackgroundColor = interpolateColor(x, {
    inputRange: [separatorSize, threshold],
    outputRange: stickyItemBackgroundColors,
  });

  const animatedShadowRadius = interpolate(x, {
    inputRange: [separatorSize, threshold],
    outputRange: [1, 16],
    extrapolate: Extrapolate.CLAMP,
  });
  //#endregion

  //#region styles
  const containerStyle = [
    styles.container,
    {
      transform: animatedTransform,
    },
    Platform.OS === 'ios'
      ? {
          shadowRadius: animatedShadowRadius,
        }
      : {},
  ];
  //#endregion

  // render
  return (
    <AnimatedSvg
      pointerEvents="none"
      style={containerStyle}
      width={itemWidth}
      height={itemHeight}
      viewBox={`0 0 ${itemWidth} ${itemHeight}`}
    >
      <AnimatedPath
        d={animatedPathData}
        fill={animatedBackgroundColor}
        fillRule="evenodd"
      />
    </AnimatedSvg>
  );
}
Example #18
Source File: index.tsx    From react-native-bottomsheet-reanimated with MIT License 4 votes vote down vote up
Index = forwardRef(
  (
    {
      isBackDropDismissByPress,
      initialPosition = { y: 0 },
      onChangeSnap,
      onChangeKeyboardAwareSnap,
      snapPoints,
      bottomSheerColor = '#FFFFFF',
      backDropColor = '#000000',
      isRoundBorderWithTipHeader = false,
      tipHeaderRadius = 12,
      header,
      body,
      isBackDrop = false,
      isModal,
      dragEnabled = true,
      isAnimatedYFromParent,
      animatedValueY,
      containerStyle,
      bodyContainerStyle = {},
      tipStyle,
      headerStyle,
      bodyStyle,
      onClose,
      bounce = 0.5,
      keyboardAware = false,
      keyboardAwareExtraSnapHeight = 0,
      keyboardAwareDrag = false,
      overDrag = true,
    }: BottomSheetProps,
    ref
  ) => {
    const [keyboardHeight] = useKeyboard(keyboardAware);
    const [headerHeight, setHeaderHeight] = useState(0);
    const [currentSnap, setCurrentSnap] = useState(initialPosition);
    const currentNomalizeSnap = useMemo(() => normalize(currentSnap), [
      currentSnap,
    ]);
    const normalizeSnap = useMemo(() => getNormalizeSnaps(snapPoints), [
      snapPoints,
    ]);
    const [_deltaY] = useState(new Animated.Value(Screen.height));
    const bottomPanel = useRef<any>();
    const _snapPoints = useMemo(() => getSnapPoints(normalizeSnap), [
      normalizeSnap,
    ]);
    const boundaries = useMemo(
      () =>
        overDrag
          ? { top: isModal ? 0 : -300, bounce: bounce }
          : getOverDragBoundries(normalizeSnap),
      [overDrag, isModal, bounce, normalizeSnap]
    );
    const _initialPosition = useMemo(
      () => getInitialPosition(initialPosition),
      [initialPosition]
    );
    const isDismissWithPress = isBackDropDismissByPress
      ? isBackDropDismissByPress
      : false;
    const [
      isBottomSheetDismissed,
      setIsBottomSheetDismissed,
    ] = useState<boolean>(initialPosition === 0 || initialPosition === '0%');

    const onDrawerSnap = (snap: any) => {
      const index = snap.nativeEvent.index;
      const value = snapPoints[index];
      setCurrentSnap(value); //
      if (value === 0 || value === '0%') {
        setIsBottomSheetDismissed(true);
        onClose && onClose();
      } else {
        setIsBottomSheetDismissed(false);
      }
      onChangeSnap && onChangeSnap({ index, value });
    };

    const dismissBottomSheet = () => {
      let index = snapPoints.findIndex(
        (x: number | string) => x === 0 || x === '0%'
      );
      if (index !== -1) {
        bottomPanel.current.snapTo({ index });
        onClose && onClose();
      }
      Keyboard.dismiss();
    };

    const snapTo = (index: number) => {
      if (snapPoints.findIndex((x) => x === 0 || x === '0%') !== -1) {
        Keyboard.dismiss();
      }
      bottomPanel.current.snapTo({ index });
      const value = snapPoints[index];
      onChangeSnap && onChangeSnap({ index, value });
    };

    useImperativeHandle(ref, () => ({
      snapTo,
      dismissBottomSheet,
    }));

    useEffect(() => {
      if (keyboardAware) {
        const currentSnapHeight = normalize(currentSnap);
        if (keyboardHeight) {
          const newSnapHeight = currentSnapHeight + keyboardHeight;
          if (newSnapHeight > Screen.height) {
            bottomPanel.current.snapToPosition({
              x: 0,
              y: 0 - keyboardAwareExtraSnapHeight,
            });
            onChangeKeyboardAwareSnap &&
              onChangeKeyboardAwareSnap({
                previousSnap: currentSnapHeight,
                nextSnap: 0,
                keyboardHeight,
              });
          } else {
            bottomPanel.current.snapToPosition({
              x: 0,
              y: Screen.height - newSnapHeight - keyboardAwareExtraSnapHeight,
            });
            onChangeKeyboardAwareSnap &&
              onChangeKeyboardAwareSnap({
                previousSnap: currentSnapHeight,
                nextSnap: newSnapHeight,
                keyboardHeight,
              });
          }
        } else {
          bottomPanel.current.snapToPosition({
            x: 0,
            y: Screen.height - currentSnapHeight,
          });
        }
      }
    }, [keyboardHeight]);

    const dragHandler = () => {
      if (dragEnabled) {
        if (!keyboardAwareDrag && keyboardHeight > 0) {
          return false;
        } else {
          return true;
        }
      }
      return false;
    };

    return (
      <View style={styles.panelContainer} pointerEvents={'box-none'}>
        {/* Backdrop */}
        {isBackDrop && (
          <Animated.View
            pointerEvents={!isBottomSheetDismissed ? 'auto' : 'box-none'}
            style={[
              styles.panelContainer,
              {
                backgroundColor: backDropColor,
                opacity: isAnimatedYFromParent
                  ? animatedValueY.interpolate({
                      inputRange: [0, Screen.height - 100],
                      outputRange: [1, 0],
                      extrapolateRight: Extrapolate.CLAMP,
                    })
                  : _deltaY.interpolate({
                      inputRange: [0, Screen.height - 100],
                      outputRange: [1, 0],
                      extrapolateRight: Extrapolate.CLAMP,
                    }),
              },
            ]}
          />
        )}

        <Interactable.View
          dragEnabled={isModal ? false : dragHandler()}
          verticalOnly={true}
          ref={bottomPanel}
          snapPoints={_snapPoints}
          initialPosition={_initialPosition}
          boundaries={boundaries}
          animatedValueY={isAnimatedYFromParent ? animatedValueY : _deltaY}
          onSnap={onDrawerSnap}
        >
          {!isModal && isDismissWithPress && !isBottomSheetDismissed && (
            <TapGestureHandler
              enabled={isBackDrop}
              onActivated={dismissBottomSheet}
            >
              <View
                style={{
                  height: Screen.height,
                  marginTop: -Screen.height,
                }}
              />
            </TapGestureHandler>
          )}

          <View
            style={[
              isModal ? styles.modal : styles.panel,
              { backgroundColor: bottomSheerColor },
              isRoundBorderWithTipHeader
                ? [
                    {
                      backgroundColor: '#FFFFFF',
                      shadowColor: '#000000',
                      shadowOffset: { width: 0, height: 0 },
                      shadowRadius: 5,
                      shadowOpacity: 0.4,
                    },
                    {
                      borderTopLeftRadius: tipHeaderRadius,
                      borderTopRightRadius: tipHeaderRadius,
                    },
                  ]
                : {},
              containerStyle,
            ]}
          >
            <View
              style={[
                isModal ? styles.modal : styles.panel,
                bodyContainerStyle,
              ]}
            >
              {!isModal && isRoundBorderWithTipHeader && (
                <View style={[styles.panelHandle, tipStyle]} />
              )}
              {!isModal && (
                <View
                  style={[styles.panelHeader, headerStyle]}
                  onLayout={(e) => setHeaderHeight(e.nativeEvent.layout.height)}
                >
                  {header}
                </View>
              )}
              <View style={bodyStyle}>
                {React.cloneElement(body, {
                  style: {
                    ...body?.props?.style,
                    height: currentNomalizeSnap - headerHeight + OFFSET,
                  },
                })}
              </View>
            </View>
          </View>
        </Interactable.View>
      </View>
    );
  }
)
Example #19
Source File: SectionListExample.tsx    From react-native-scroll-bottom-sheet with MIT License 4 votes vote down vote up
SectionListExample: React.FC<Props> = () => {
  const snapPointsFromTop = [96, '45%', windowHeight - 264];
  const animatedPosition = React.useRef(new Value(0.5));
  const handleLeftRotate = concat(
    interpolate(animatedPosition.current, {
      inputRange: [0, 0.4, 1],
      outputRange: [25, 0, 0],
      extrapolate: Extrapolate.CLAMP,
    }),
    'deg'
  );
  const handleRightRotate = concat(
    interpolate(animatedPosition.current, {
      inputRange: [0, 0.4, 1],
      outputRange: [-25, 0, 0],
      extrapolate: Extrapolate.CLAMP,
    }),
    'deg'
  );
  const cardScale = interpolate(animatedPosition.current, {
    inputRange: [0, 0.6, 1],
    outputRange: [1, 1, 0.9],
    extrapolate: Extrapolate.CLAMP,
  });

  const renderSectionHeader = React.useCallback(
    ({ section }) => (
      <View style={styles.section}>
        <Text>{section.title}</Text>
      </View>
    ),
    []
  );

  const renderItem = React.useCallback(
    ({ item }) => <Transaction {...item} />,
    []
  );

  return (
    <View style={styles.container}>
      <View style={styles.balanceContainer}>
        <Text style={styles.poundSign}>£</Text>
        <Text style={styles.balance}>4,345</Text>
      </View>
      <ProgressBar
        style={styles.progressBar}
        progress={0.8}
        color={Colors.green600}
      />
      <Animated.Image
        source={require('../assets/card-front.png')}
        style={[styles.card, { transform: [{ scale: cardScale }] }]}
      />
      <View style={styles.row}>
        <View>
          <View style={styles.action}>
            <FontAwesome5 name="credit-card" size={24} color="black" />
          </View>
          <Text style={{ textAlign: 'center' }}>Account</Text>
        </View>
        <View>
          <View style={styles.action}>
            <FontAwesome5 name="eye" size={24} color="black" />
          </View>
          <Text style={{ textAlign: 'center' }}>Pin</Text>
        </View>
        <View>
          <View style={styles.action}>
            <Ionicons name="md-snow" size={24} color="black" />
          </View>
          <Text style={{ textAlign: 'center' }}>Freeze</Text>
        </View>
        <View>
          <View style={styles.action}>
            <FontAwesome5 name="plus" size={24} color="black" />
          </View>
          <Text style={{ textAlign: 'center' }}>Top up</Text>
        </View>
      </View>
      <ScrollBottomSheet<ListItemData>
        enableOverScroll
        removeClippedSubviews={Platform.OS === 'android' && sections.length > 0}
        componentType="SectionList"
        topInset={statusBarHeight + navBarHeight}
        animatedPosition={animatedPosition.current}
        snapPoints={snapPointsFromTop}
        initialSnapIndex={1}
        animationConfig={{
          easing: Easing.inOut(Easing.linear),
        }}
        renderHandle={() => (
          <Handle style={{ paddingVertical: 20, backgroundColor: '#F3F4F9' }}>
            <Animated.View
              style={[
                styles.handle,
                {
                  left: windowWidth / 2 - 20,
                  transform: [{ rotate: handleLeftRotate }],
                },
              ]}
            />
            <Animated.View
              style={[
                styles.handle,
                {
                  right: windowWidth / 2 - 20,
                  transform: [{ rotate: handleRightRotate }],
                },
              ]}
            />
          </Handle>
        )}
        contentContainerStyle={styles.contentContainerStyle}
        stickySectionHeadersEnabled
        sections={sections}
        keyExtractor={i => i.id}
        renderSectionHeader={renderSectionHeader}
        renderItem={renderItem}
      />
    </View>
  );
}
Example #20
Source File: index.tsx    From react-native-scroll-bottom-sheet with MIT License 4 votes vote down vote up
constructor(props: Props<T>) {
    super(props);
    const { initialSnapIndex, animationType } = props;

    const animationDriver = animationType === 'timing' ? 0 : 1;
    const animationDuration =
      (props.animationType === 'timing' && props.animationConfig?.duration) ||
      DEFAULT_ANIMATION_DURATION;

    const ScrollComponent = this.getScrollComponent();
    // @ts-ignore
    this.scrollComponent = Animated.createAnimatedComponent(ScrollComponent);

    const snapPoints = this.getNormalisedSnapPoints();
    const openPosition = snapPoints[0];
    const closedPosition = this.props.enableOverScroll
      ? windowHeight
      : snapPoints[snapPoints.length - 1];
    const initialSnap = snapPoints[initialSnapIndex];
    this.nextSnapIndex = new Value(initialSnapIndex);

    const initialDecelerationRate = Platform.select({
      android:
        props.initialSnapIndex === 0 ? ANDROID_NORMAL_DECELERATION_RATE : 0,
      ios: IOS_NORMAL_DECELERATION_RATE,
    });
    this.decelerationRate = new Value(initialDecelerationRate);

    const handleGestureState = new Value<GestureState>(-1);
    const handleOldGestureState = new Value<GestureState>(-1);
    const drawerGestureState = new Value<GestureState>(-1);
    const drawerOldGestureState = new Value<GestureState>(-1);

    const lastSnapInRange = new Value(1);
    this.prevTranslateYOffset = new Value(initialSnap);
    this.translationY = new Value(initialSnap);

    this.lastSnap = new Value(initialSnap);

    this.onHandleGestureEvent = event([
      {
        nativeEvent: {
          translationY: this.dragY,
          oldState: handleOldGestureState,
          state: handleGestureState,
          velocityY: this.velocityY,
        },
      },
    ]);
    this.onDrawerGestureEvent = event([
      {
        nativeEvent: {
          translationY: this.dragY,
          oldState: drawerOldGestureState,
          state: drawerGestureState,
          velocityY: this.velocityY,
        },
      },
    ]);
    this.onScrollBeginDrag = event([
      {
        nativeEvent: {
          contentOffset: { y: this.lastStartScrollY },
        },
      },
    ]);

    const didHandleGestureBegin = eq(handleGestureState, GestureState.ACTIVE);

    const isAnimationInterrupted = and(
      or(
        eq(handleGestureState, GestureState.BEGAN),
        eq(drawerGestureState, GestureState.BEGAN),
        and(
          eq(this.isAndroid, 0),
          eq(animationDriver, 1),
          or(
            eq(drawerGestureState, GestureState.ACTIVE),
            eq(handleGestureState, GestureState.ACTIVE)
          )
        )
      ),
      clockRunning(this.animationClock)
    );

    this.didGestureFinish = or(
      and(
        eq(handleOldGestureState, GestureState.ACTIVE),
        eq(handleGestureState, GestureState.END)
      ),
      and(
        eq(drawerOldGestureState, GestureState.ACTIVE),
        eq(drawerGestureState, GestureState.END)
      )
    );

    // Function that determines if the last snap point is in the range {snapPoints}
    // In the case of interruptions in the middle of an animation, we'll get
    // lastSnap values outside the range
    const isLastSnapPointInRange = (i: number = 0): Animated.Node<number> =>
      i === snapPoints.length
        ? lastSnapInRange
        : cond(
            eq(this.lastSnap, snapPoints[i]),
            [set(lastSnapInRange, 1)],
            isLastSnapPointInRange(i + 1)
          );

    const scrollY = [
      set(lastSnapInRange, 0),
      isLastSnapPointInRange(),
      cond(
        or(
          didHandleGestureBegin,
          and(
            this.isManuallySetValue,
            not(eq(this.manualYOffset, snapPoints[0]))
          )
        ),
        [set(this.dragWithHandle, 1), 0]
      ),
      cond(
        // This is to account for a continuous scroll on the drawer from a snap point
        // Different than top, bringing the drawer to the top position, so that if we
        // change scroll direction without releasing the gesture, it doesn't pull down the drawer again
        and(
          eq(this.dragWithHandle, 1),
          greaterThan(snapPoints[0], add(this.lastSnap, this.dragY)),
          and(not(eq(this.lastSnap, snapPoints[0])), lastSnapInRange)
        ),
        [
          set(this.lastSnap, snapPoints[0]),
          set(this.dragWithHandle, 0),
          this.lastStartScrollY,
        ],
        cond(eq(this.dragWithHandle, 1), 0, this.lastStartScrollY)
      ),
    ];

    this.didScrollUpAndPullDown = cond(
      and(
        greaterOrEq(this.dragY, this.lastStartScrollY),
        greaterThan(this.lastStartScrollY, 0)
      ),
      set(this.scrollUpAndPullDown, 1)
    );

    this.setTranslationY = cond(
      and(
        not(this.dragWithHandle),
        not(greaterOrEq(this.dragY, this.lastStartScrollY))
      ),
      set(this.translationY, sub(this.dragY, this.lastStartScrollY)),
      set(this.translationY, this.dragY)
    );

    this.extraOffset = cond(
      eq(this.scrollUpAndPullDown, 1),
      this.lastStartScrollY,
      0
    );
    const endOffsetY = add(
      this.lastSnap,
      this.translationY,
      multiply(1 - props.friction, this.velocityY)
    );

    this.calculateNextSnapPoint = (i = 0): Animated.Node<number> | number =>
      i === snapPoints.length
        ? this.tempDestSnapPoint
        : cond(
            greaterThan(
              abs(sub(this.tempDestSnapPoint, endOffsetY)),
              abs(sub(add(snapPoints[i], this.extraOffset), endOffsetY))
            ),
            [
              set(this.tempDestSnapPoint, add(snapPoints[i], this.extraOffset)),
              set(this.nextSnapIndex, i),
              this.calculateNextSnapPoint(i + 1),
            ],
            this.calculateNextSnapPoint(i + 1)
          );

    const runAnimation = ({
      clock,
      from,
      to,
      position,
      finished,
      velocity,
      frameTime,
    }: TimingParams) => {
      const state = {
        finished,
        velocity: new Value(0),
        position,
        time: new Value(0),
        frameTime,
      };

      const timingConfig = {
        duration: animationDuration,
        easing:
          (props.animationType === 'timing' && props.animationConfig?.easing) ||
          DEFAULT_EASING,
        toValue: new Value(0),
      };

      const springConfig = {
        ...DEFAULT_SPRING_PARAMS,
        ...((props.animationType === 'spring' && props.animationConfig) || {}),
        toValue: new Value(0),
      };

      return [
        cond(and(not(clockRunning(clock)), not(eq(finished, 1))), [
          // If the clock isn't running, we reset all the animation params and start the clock
          set(state.finished, 0),
          set(state.velocity, velocity),
          set(state.time, 0),
          set(state.position, from),
          set(state.frameTime, 0),
          set(timingConfig.toValue, to),
          set(springConfig.toValue, to),
          startClock(clock),
        ]),
        // We run the step here that is going to update position
        cond(
          eq(animationDriver, 0),
          timing(clock, state, timingConfig),
          spring(clock, state, springConfig)
        ),
        cond(
          state.finished,
          [
            call([this.nextSnapIndex], ([value]) => {
              if (value !== this.prevSnapIndex) {
                this.props.onSettle?.(value);
              }
              this.prevSnapIndex = value;
            }),
            // Resetting appropriate values
            set(drawerOldGestureState, GestureState.END),
            set(handleOldGestureState, GestureState.END),
            set(this.prevTranslateYOffset, state.position),
            cond(eq(this.scrollUpAndPullDown, 1), [
              set(
                this.prevTranslateYOffset,
                sub(this.prevTranslateYOffset, this.lastStartScrollY)
              ),
              set(this.lastStartScrollY, 0),
              set(this.scrollUpAndPullDown, 0),
            ]),
            cond(eq(this.destSnapPoint, snapPoints[0]), [
              set(this.dragWithHandle, 0),
            ]),
            set(this.isManuallySetValue, 0),
            set(this.manualYOffset, 0),
            stopClock(clock),
            this.prevTranslateYOffset,
          ],
          // We made the block return the updated position,
          state.position
        ),
      ];
    };

    const translateYOffset = cond(
      isAnimationInterrupted,
      [
        // set(prevTranslateYOffset, animationPosition) should only run if we are
        // interrupting an animation when the drawer is currently in a different
        // position than the top
        cond(
          or(
            this.dragWithHandle,
            greaterOrEq(abs(this.prevDragY), this.lastStartScrollY)
          ),
          set(this.prevTranslateYOffset, this.animationPosition)
        ),
        set(this.animationFinished, 1),
        set(this.translationY, 0),
        // Resetting appropriate values
        set(drawerOldGestureState, GestureState.END),
        set(handleOldGestureState, GestureState.END),
        // By forcing that frameTime exceeds duration, it has the effect of stopping the animation
        set(this.animationFrameTime, add(animationDuration, 1000)),
        set(this.velocityY, 0),
        stopClock(this.animationClock),
        this.prevTranslateYOffset,
      ],
      cond(
        or(
          this.didGestureFinish,
          this.isManuallySetValue,
          clockRunning(this.animationClock)
        ),
        [
          runAnimation({
            clock: this.animationClock,
            from: cond(
              this.isManuallySetValue,
              this.prevTranslateYOffset,
              add(this.prevTranslateYOffset, this.translationY)
            ),
            to: this.destSnapPoint,
            position: this.animationPosition,
            finished: this.animationFinished,
            frameTime: this.animationFrameTime,
            velocity: this.velocityY,
          }),
        ],
        [
          set(this.animationFrameTime, 0),
          set(this.animationFinished, 0),
          // @ts-ignore
          this.prevTranslateYOffset,
        ]
      )
    );

    this.translateY = interpolate(
      add(translateYOffset, this.dragY, multiply(scrollY, -1)),
      {
        inputRange: [openPosition, closedPosition],
        outputRange: [openPosition, closedPosition],
        extrapolate: Extrapolate.CLAMP,
      }
    );

    this.position = interpolate(this.translateY, {
      inputRange: [openPosition, snapPoints[snapPoints.length - 1]],
      outputRange: [1, 0],
      extrapolate: Extrapolate.CLAMP,
    });
  }
Example #21
Source File: InstagramFeed.tsx    From react-native-gallery-toolkit with MIT License 4 votes vote down vote up
function RenderItem({
  index: _index,
  activeItemIndex,
  item: { images, name },
  setControlsHidden,
  scrollViewRef,
}: RenderItemProps) {
  const opacity = useSharedValue(0);
  const backgroundScale = useSharedValue(0);
  const activeIndexInPager = useSharedValue(0);

  const normalizedImages = useMemo(
    () =>
      images.map((item) => {
        const { targetWidth, targetHeight } = normalizeDimensions(
          item,
        );

        return {
          ...item,
          width: targetWidth,
          height: targetHeight,
        };
      }),
    [images],
  );

  const onScale = useWorkletCallback((scale: number) => {
    opacity.value = interpolate(
      scale,
      [1, 2],
      [0, 0.7],
      Extrapolate.CLAMP,
    );

    backgroundScale.value = interpolate(
      scale,
      [1, 1.01, 2],
      [0, 4, 5],
      Extrapolate.CLAMP,
    );
  }, []);

  const onGestureStart = useWorkletCallback(() => {
    setControlsHidden(true);
    runOnJS(StatusBar.setHidden)(true);
    activeItemIndex.value = _index;
  }, []);

  const onGestureRelease = useWorkletCallback(() => {
    //delay for smooth hiding background opacity
    activeItemIndex.value = withDelay(200, withTiming(-1));
    setControlsHidden(false);
    runOnJS(StatusBar.setHidden)(false);
  }, []);

  const overlayStyles = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
      transform: [
        {
          scale: backgroundScale.value,
        },
      ],
    };
  });

  const keyExtractor = useCallback(
    ({ id }: { id: string }) => id,
    [],
  );

  const canvasHeight = useMemo(
    () => Math.max(...normalizedImages.map((item) => item.height)),
    [normalizedImages],
  );

  const renderPage = useCallback(({ item, pagerRefs }: RenderPageProps<SimpleGalleryItemType>) => {
    return (
      <ScalableImage
        outerGestureHandlerRefs={[...pagerRefs, scrollViewRef]}
        source={item.uri}
        width={item.width}
        height={item.height}
        onScale={onScale}
        onGestureStart={onGestureStart}
        onGestureRelease={onGestureRelease}
      />
    );
  }, []);

  const onIndexChangeWorklet = useWorkletCallback((nextIndex: number) => {
    activeIndexInPager.value = nextIndex;
  }, []);

  const content = (() => {
    if (images.length === 1) {
      return (
        <ScalableImage
          source={images[0].uri}
          width={images[0].width}
          height={images[0].height}
          onScale={onScale}
          outerGestureHandlerRefs={[scrollViewRef]}
          onGestureStart={onGestureStart}
          onGestureRelease={onGestureRelease}
        />
      );
    } else {
      return (
        <>
          <Pager
            pages={images}
            totalCount={images.length}
            keyExtractor={keyExtractor}
            initialIndex={0}
            width={width}
            gutterWidth={0}
            outerGestureHandlerRefs={[scrollViewRef]}
            verticallyEnabled={false}
            // @ts-expect-error
            renderPage={renderPage}
            onIndexChange={onIndexChangeWorklet}
          />

          <Pagination
            length={images.length}
            activeIndexInPager={activeIndexInPager}
          />
        </>
      );
    }
  })();

  return (
    <Animated.View style={s.itemContainer}>
      <Header uri={images[0].uri} name={name} />

      <Animated.View
        pointerEvents="none"
        style={[s.overlay, overlayStyles]}
      />

      <View style={[s.itemPager, { height: canvasHeight }]}>
        {content}
      </View>

      <Footer />
    </Animated.View>
  );
}
Example #22
Source File: Bar.tsx    From expo-progress with MIT License 4 votes vote down vote up
function ProgressBar({
  isIndeterminate = false,
  duration = isIndeterminate ? 1000 : 500,
  isAnimated = false,
  progress = isIndeterminate ? 0.5 : 0,
  height = 7,
  borderRadius = height * 0.5,
  // Default iOS blue
  color = '#007aff',
  trackColor = 'transparent',
  style,
  trackImage,
  progressImage,
}: ProgressBarProps) {
  const [width, setWidth] = React.useState(0);
  const progressValue = useValue(isAnimated ? 0 : progress);
  const indeterminateValue = useValue(0);
  const animatedWidth = interpolate(
    clamp(progressValue, minProgress, maxProgress),
    {
      inputRange: [minProgress, maxProgress],
      outputRange: [0, width],
      extrapolate: Extrapolate.CLAMP,
    }
  );

  useCode(() => {
    if (isAnimated) {
      return set(
        progressValue,
        timing({
          from: progressValue,
          to: progress,
          duration,
        })
      );
    } else {
      return set(progressValue, progress);
    }
  }, [progress]);

  useCode(() => {
    if (isIndeterminate) {
      const loopingIndeterminateValue = loop({
        autoStart: true,
        boomerang: false,
        duration,
      });
      return set(indeterminateValue, loopingIndeterminateValue);
    }
    const animatedIndeterminateValue = timing({
      from: indeterminateValue,
      to: 0,
    });
    return set(indeterminateValue, animatedIndeterminateValue);
  }, [isIndeterminate]);

  // todo: web has a bug where the reanimated Animated.View style is not updating unless this is an animated value.
  let translateX: Node<number> | number = useValue(0);

  if (isIndeterminate) {
    translateX = interpolate(indeterminateValue, {
      inputRange: [0, 1],
      outputRange: [multiply(-1, animatedWidth), width],
    });
  }

  return (
    <ImageBackground
      onLayout={(e: LayoutChangeEvent) => {
        setWidth(e.nativeEvent.layout.width);
      }}
      resizeMode={'stretch'}
      style={[
        styles.container,
        {
          height,
          borderRadius,
          backgroundColor: trackColor,
        },
        style,
      ]}
      // @ts-ignore
      source={trackImage}
    >
      <Animated.Image
        style={[
          styles.bar,
          {
            width: animatedWidth,
            transform: [
              {
                translateX,
              },
            ],
            backgroundColor: color,
            borderRadius,
          },
        ]}
        // @ts-ignore
        source={progressImage}
      />
    </ImageBackground>
  );
}