react native 抖音视频列表页

实现效果

需要实现的效果主要有两个,一个是上下翻页效果,还有就是点赞的动画。

效果 (好像不支持 webp 格式图片 只能将就一下)

列表页上下翻页实现

播放视频使用 react-native-video 库。列表使用 FlatList,每个 item 占用一整屏,配合 pagingEnabled 属性可以翻页效果。
通过 onViewableItemsChanged 来控制只有当前的页面才播放视频。

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

  const _onViewableItemsChanged = useCallback(({ viewableItems }) => {
    // 这个方法为了让state对应当前呈现在页面上的item的播放器的state
    // 也就是只会有一个播放器播放,而不会每个item都播放
    // 可以理解为,只要不是当前再页面上的item 它的状态就应该暂停
    // 只有100%呈现再页面上的item(只会有一个)它的播放器是播放状态
    if (viewableItems.length === 1) {
      setCurrentItem(viewableItems[0].index);
    }
  }, []);

  useEffect(() => {
    const mockData = [];
    for (let i = 0; i < 100; i++) {
      mockData.push({ id: i, pause: false });
    }
    setData(mockData);
  }, []);

  return (
    
      
      
        onMoveShouldSetResponder={() => true}
        data={data}
        renderItem={({ item, index }) => (
          
        )}
        pagingEnabled={true}
        getItemLayout={(item, index) => {
          return { length: HEIGHT, offset: HEIGHT * index, index };
        }}
        onViewableItemsChanged={_onViewableItemsChanged}
        keyExtractor={(item, index) => index.toString()}
        viewabilityConfig={{
          viewAreaCoveragePercentThreshold: 80, // item滑动80%部分才会到下一个
        }}
      />
    
  );
};

点赞效果

单次点击的时候切换暂停/播放状态,连续多次点击每次在点击位置出现一个爱心,随机旋转一个角度,爱心先放大再变透明消失。

爱心动画实现

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

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

    return (
      
    );
  },
  () => true,
);

连续点赞判定

监听手势,记录每次点击时间 lastClickTime,设置 CLICK_THRESHOLD 连续两次点击事件间隔小于 CLICK_THRESHOLD 视为连续点击,在点击位置创建爱心,添加到 heartList,否则视为单次点击,暂停播放。

const ShortVideoItem = React.memo((props: ShortVideoItemProps) => {
  const [paused, setPaused] = React.useState(props.paused);
  const [data, setData] = React.useState();
  const [heartList, setHeartList] = React.useState([]);
  const lastClickTime = React.useRef(0); // 记录上次点击时间
  const pauseHandler = React.useRef();

  useEffect(() => {
    setTimeout(() => {
      setData({
        video: TEST_VIDEO,
        hasFavor: false,
      });
    });
  }, []);

  useEffect(() => {
    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();
      // 连续点击
      if (currentTime - lastClickTime.current < CLICK_THRESHOLD) {
        pauseHandler.current && clearTimeout(pauseHandler.current);
        _addHeartView(heartViewData);
        if (data && !data.hasFavor) {
          _favor(false, false);
        }
      } else {
        pauseHandler.current = setTimeout(() => {
          setPaused(preValue => !preValue);
        }, CLICK_THRESHOLD);
      }

      lastClickTime.current = currentTime;
    }, [_addHeartView, _favor, data],
  );

  return  true}
    onResponderGrant={_handlerClick}
    style={{ height: HEIGHT }}
  >
    {
      data
        ? 

手势冲突

通过 GestureResponder 拦截点击事件之后会造成 FlatList 滚动事件失效,所以需要将滚动事件交给 FlatList。通过 onResponderTerminationRequest 属性可以让 View 放弃处理事件的权利,将滚动事件交给 FlatList来处理。

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

代码

MyTiktok

参考文献

https://blog.csdn.net/qq_38356174/article/details/96439456
https://juejin.im/post/5b504823e51d451953125799

你可能感兴趣的:(react native 抖音视频列表页)