4TWIGGERS / react-native-fresh-refresh

custom pull to refresh component
MIT License
183 stars 19 forks source link

Support Loader animation execution progress and custom animation. #12

Open 1111mp opened 2 years ago

1111mp commented 2 years ago

You can control the progress of the animation and custom animation for Loader when customizing the Loader with Lottie animation. Custom animation only supports customizing the transform property of the loader.

/**
   * @description: custom animation for Loader.
   * @param loading Is it in loading state.
   * @param offsetY Offset a to scroll down.
   * @param callback For stop progress animation.
   * @return CSS transform property animation.
   */
  onCustomAnimation: (
    loading: boolean,
    offsetY: number,
    callback: (flag?: boolean) => void,
  ) => TransformsStyle['transform'];

Like this:

https://user-images.githubusercontent.com/31227919/164958124-8c8c5ef3-4e61-4cb8-a8f7-724807e48492.mov

Demo code:

import LottieView from 'lottie-react-native';
const AnimatedLottieView = Animated.createAnimatedComponent(LottieView);

const onCustomAnimation = (
  loading: boolean,
  offsetY: number,
  callback: (flag?: boolean) => void, // For stop progress animation.
) => {
  'worklet'; // must be
  return [
    {
      translateY: loading
        ? interpolate(offsetY, [0, 60], [-60, 0], Extrapolate.CLAMP)
        : withTiming(-60, undefined, flag => {
            callback(flag);
          }),
    },
  ];
};

<FlatListWithRefresh
  refreshHeight={60}
  onCustomAnimation={onCustomAnimation}
  contentOffset={contentOffset}
  Loader={({animatedProps}) => {
    return (
      <AnimatedLottieView
        ref={loaderRef}
        source={require('../../components/FlatListWithRefresh/bouncing-fruits.json')}
        animatedProps={animatedProps}
        // autoPlay
      />
    );
  }}
  isLoading={isLoading}
  // managedLoading={isLoading}
  onRefresh={() => {
    refreshSimulationHandler();
  }}>
  <Animated.FlatList
    data={listData}
    // bounces={false}
    keyExtractor={(item: string) => item}
    renderItem={({item}) => {
      return (
        <View key={item} style={styles.row}>
          <Text style={styles.rowTitle}>{item}</Text>
        </View>
      );
    }}
    style={styles.scrollList}
    contentContainerStyle={styles.contenContainer}
    // showsVerticalScrollIndicator={false}
    scrollEventThrottle={16}
    ListEmptyComponent={() => (
      <View>
        <Text>Empty</Text>
      </View>
    )}
  />
</FlatListWithRefresh>;

When the refresh is over, the animation will be terminated(The purpose of adding a View with a yellow background is to facilitate viewing the status of the Loader when it is hidden):

https://user-images.githubusercontent.com/31227919/164958300-f666060a-18cc-4b60-bf16-c3f20f1ea8d5.mov

alexandrius commented 2 years ago

Hey! Sorry for the delay. What is benefit of this PR over listening contentOffset prop?

1111mp commented 2 years ago

Yes, i'm just expressing my own feelings. Feel free to correct me if I'm wrong. I think the reason people started looking for an alternative to the official RefreshControl component is because it can't be customized. And being able to control the playback progress of the animation and customize the appearing and hiding animations for the loader should be very urgent.

I tried to use offsetContent to control the progress of the animation, but the offsetContent doesn't change every frame, it only fires a few times after the finger is released. I can't control the progress of the animation as the finger slides down based on it. (I believe this should be a very common product scheme...) Then I also can't implement the animation of appearing and hiding of the custom loader component. It is not possible to get whether the loader should start to activate or needs to be canceled based on the existing props, because I can't get the status of isLoaderActive.

console.log(contentOffset.value);
//  ......
<RefreshableWrapper
  refreshHeight={60}
  // onCustomAnimation={onCustomAnimation}
  contentOffset={contentOffset}
  Loader={() => {
    console.log(contentOffset.value);
    const progressVal = contentOffset.value / 1.2 / 60;
    return (
      // <Animated.View style={[]}>
      <AnimatedLottieView
        ref={loaderRef}
        source={require('../../components/FlatListWithRefresh/bouncing-fruits.json')}
        animatedProps={{
          progress: progressVal > 1 ? 1 : progressVal,
        }}
        // autoPlay
      />
      // </Animated.View>
    );
  }}
  isLoading={isLoading}
  // managedLoading={isLoading}
  onRefresh={() => {
    refreshSimulationHandler();
  }}>
  <Animated.FlatList
    data={listData}
    // bounces={false}
    keyExtractor={(item: string) => item}
    renderItem={({item}) => {
      return (
        <View key={item} style={styles.row}>
          <Text style={styles.rowTitle}>{item}</Text>
        </View>
      );
    }}
    style={styles.scrollList}
    contentContainerStyle={styles.contenContainer}
    // showsVerticalScrollIndicator={false}
    scrollEventThrottle={16}
    ListEmptyComponent={() => (
      <View>
        <Text>Empty</Text>
      </View>
    )}
  />
</RefreshableWrapper>;