Implementation effect

There are two main effects that need to be realized, one is the up-and-down page-turning effect, and the animation that has been praised.

List page up and down page implementation

Use the React-native video library to play videos. The list uses a FlatList, where each item occupies a full screen and the pagingEnabled attribute is used to turn pages. Use onViewableItemsChanged to control that only the current page plays the video.

const ShortVideoPage = (a)= > {
  const [currentItem, setCurrentItem] = useState(0);
  const [data, setData] = useState<ItemData[]>([]);

  const _onViewableItemsChanged = useCallback(({ viewableItems }) = > {
    // This method makes state correspond to the player state of the item currently rendered on the page
    // Only one player will play, not every item will play
    // The state should be paused as long as it is not the current item on the page
    // Only 100% of the items on the page are rendered (there is only one item) and its player is in play state
    if (viewableItems.length === 1) {
      setCurrentItem(viewableItems[0].index); }} []); useEffect((a)= > {
    const mockData = [];
    for (let i = 0; i < 100; i++) {
      mockData.push({ id: i, pause: false}); } setData(mockData); } []);return( <View style={{ flex: 1 }}> <StatusBar backgroundColor="transparent" translucent /> <FlatList<ItemData> onMoveShouldSetResponder={() => true} data={data} renderItem={({ item, index }) => ( <ShortVideoItem paused={index ! == currentItem} id={item.id} /> )} pagingEnabled={true} getItemLayout={(item, index) => { return { length: HEIGHT, offset: HEIGHT * index, index }; }} onViewableItemsChanged={_onViewableItemsChanged} keyExtractor={(item, index) => index.toString()} viewabilityConfig={{ viewAreaCoveragePercentThreshold: }} /> </View>); };Copy the code

Thumb up effect

When a single click, switch the pause/play state. Click repeatedly and a love will appear at the click position each time. Rotate an Angle randomly.

Realization of love animation

const AnimatedHeartView = React.memo(
  (props: AnimatedHeartProps) = > {
    // [-25, 25] random Angle
    const rotateAngle = `The ${Math.round(Math.random() * 50 - 25)}deg`;
    const animValue = React.useRef(new Animated.Value(0)).current;

    React.useEffect((a)= > {
      Animated.sequence([
        Animated.spring(animValue, {
          toValue: 1.useNativeDriver: true.bounciness: 5,
        }),
        Animated.timing(animValue, {
          toValue: 2.useNativeDriver: true,
        }),
      ]).start((a)= > {
        props.onAnimFinished();
      });
    }, [animValue, props]);

    return (
      <Animated.Image
        style={{
          position: 'absolute',
          width: 108.height: 126.top: props.y - 100.left: props.x - 54.opacity: animValue.interpolate({
            inputRange: [0.1.2].outputRange: [1.1.0],}).transform:[{scale: animValue.interpolate({
                inputRange: [0.1.2].outputRange: [1.5.1.0.2],})}, {rotate: rotateAngle,}]}}source={require('./img/heart.webp')} / >
    );
  },
  () => true,
);
Copy the code

Continuous like rule

Monitor gestures, record each click time lastClickTime, set CLICK_THRESHOLD for two consecutive click events less than the CLICK_THRESHOLD as a continuous click, create a heart at the click position, add it to the heartList, otherwise as a single click. Pause the playback.

const ShortVideoItem = React.memo((props: ShortVideoItemProps) = > {
  const [paused, setPaused] = React.useState(props.paused);
  const [data, setData] = React.useState<VideoData>();
  const [heartList, setHeartList] = React.useState<HeartData[]>([]);
  const lastClickTime = React.useRef(0); // Record the last click time
  const pauseHandler = React.useRef<number>();

  useEffect((a)= > {
    setTimeout((a)= > {
      setData({
        video: TEST_VIDEO,
        hasFavor: false}); }); } []); useEffect((a)= > {
    setPaused(props.paused);
  }, [props.paused]);

  const _addHeartView = React.useCallback(heartViewData= > {
    setHeartList(list= >[...list, heartViewData]); } []);const _removeHeartView = React.useCallback(index= > {
    setHeartList(list= > list.filter((item, i) = > index !== i));
  }, []);

  const _favor = React.useCallback(
    (hasFavor, canCancelFavor = true) = > {
      if(! hasFavor || canCancelFavor) { setData(preValue= > (preValue ? { ...preValue, hasFavor:! hasFavor } : preValue)); }}, []);const _handlerClick = React.useCallback(
    (event: GestureResponderEvent) = > {
      const { pageX, pageY } = event.nativeEvent;
      const heartViewData = {
        x: pageX,
        y: pageY - 60.key: new Date().getTime().toString(),
      };
      const currentTime = new Date().getTime();
      // Click continuously
      if (currentTime - lastClickTime.current < CLICK_THRESHOLD) {
        pauseHandler.current && clearTimeout(pauseHandler.current);
        _addHeartView(heartViewData);
        if(data && ! data.hasFavor) { _favor(false.false); }}else {
        pauseHandler.current = setTimeout((a)= > {
          setPaused(preValue= >! preValue); }, CLICK_THRESHOLD); } lastClickTime.current = currentTime; }, [_addHeartView, _favor, data], );return<View onStartShouldSetResponder={() => true} onResponderGrant={_handlerClick} style={{ height: HEIGHT }} > { data ? <Video source={{ uri: data? .video }} style={styles.backgroundVideo} paused={paused} resizeMode={'contain'} repeat /> : null } { heartList.map(({ x, y, key }, index) => { return ( <AnimatedHeartView x={x} y={y} key={key} onAnimFinished={() => _removeHeartView(index)} /> ); }) } <View style={{ justifyContent: 'flex-end', paddingHorizontal: 22, flex: 1 }}> <View style={{ backgroundColor: Opacity: 0.8, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', marginRight: 'auto', paddingHorizontal: 8, }}> <Text style={{ fontSize: 14, color: '#FFF'}} > </Text> </View> <View style={{height: 1, marginTop: 12, backgroundColor: '#FFF' }} /> <Text style={{ marginTop: 12, color: '#FFF', fontSize: 16, fontWeight: 'Bold ',}} numberOfLines={1} > 5㎡ long toilet how to design dry and wet separation? </Text> <Text style={{marginTop: 8, color: '#FFF', opacity: 0.6, fontSize: 12,}} numberOfLines={2} > Appearance level than five-star hotel toilet also advanced, toilet, must be so installed! Appearance level than toilet, must be so installed! </Text> <View style={{ flexDirection: 'row', marginTop: 18, marginBottom: 20, alignItems: 'center', }}> <View style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: '#FFF' }} /> <Text style={{ color: '#FFF', fontSize: 14, marginLeft: </Text> </View> </View> <View style={{position: 'absolute', right: 20, bottom: 20} 165, }}> <Image style={styles.icon} source={data? .hasFavor ? require('./img/love-f.png') : Require ('./img/love.png')} /> <Text style={styles.countnumber}>1.2w</Text> <Image style={styles.icon} Source ={require('./img/collect.png')} /> <Text style={styles.countnumber}>1.2w</Text> <Image style={styles.icon} Source ={require('./img/comment.png')} /> <Text style={styles.countnumber}>1.2w</Text> </View> {paused? <Image style={{ position: 'absolute', top: '50%', left: '50%', width: 40, height: 40, marginLeft: -20, marginTop: -20, }} source={require('./img/play.webp')} /> : null } </View>; }, (preValue, nextValue) => preValue.id === nextValue.id && preValue.paused === nextValue.paused);Copy the code

Gestures conflict

Intercepting the click event through the GestureResponder invalidates the FlatList scroll event, so you need to pass the scroll event to the FlatList. Through onResponderTerminationRequest properties can give up the View right to events, the scroll event to FlatList to deal with.

 <View
      onStartShouldSetResponder={() => true}
      onResponderTerminationRequest={() => true}   <---- here
      onResponderGrant={_handlerClick}>
    {/* some code */}
</View>
Copy the code

code

MyTiktok

reference

Blog.csdn.net/qq_38356174…

Juejin. Cn/post / 684490…