You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native RecyclerListView实现视频出屏暂停(类Reels/TikTok)

解决RecyclerListView中React Native Video同时播放的问题

嘿,我之前开发类似TikTok/Reels的短视频应用时也碰到过一模一样的问题!RecyclerListView默认不会帮我们管理子组件的播放状态,所以所有视频一加载就会同时播放。不过别担心,我们可以利用RecyclerListView提供的可见行监听机制来实现仅播放屏幕内视频的功能,具体步骤如下:

核心思路

RecyclerListView有一个onVisibleIndicesChanged回调,它会在列表滚动、可见行变化时触发,告诉我们当前哪些索引的行是可见的。我们可以借助这个回调跟踪当前可见的视频索引,然后把这个状态传递给每个VideoPlayer组件,让它们根据自己是否在可见范围内来自动控制播放/暂停,同时保留用户手动暂停的交互。

步骤1:修改父组件(App.js)

首先,我们需要在父组件中添加状态来存储当前可见的索引,并实现回调函数来更新这个状态,同时把索引信息传递给每个VideoPlayer:

import VideoPlayer from './ViewVideo';
const fakeData = [
  { type: 'NORMAL', item: { uri: require('./images/likd2.mp4'), }, },
  { type: 'NORMAL', item: { uri: require('./images/Linkd.mp4'), }, },
  { type: 'NORMAL', item: { uri: require('./images/PlayDate.mp4'), }, },
];
export default class Myworld extends React.Component {
  state = {
    currentVisibleIndices: [] // 存储当前可见的视频索引
  };

  dataProvider = new DataProvider((r1, r2) => {
    return r1 !== r2;
  }).cloneWithRows(fakeData);

  layoutProvider = new LayoutProvider(
    (i) => {
      return this.dataProvider.getDataForIndex(i).type;
    },
    (type, dim) => {
      switch (type) {
        case 'NORMAL':
          dim.width = '100%';
          dim.height = '100%';
          break;
        default:
          dim.width = '100%';
          dim.height = '100%';
          break;
      }
    },
  );

  // 处理可见行变化的回调
  handleVisibleIndicesChanged = (visibleIndices) => {
    // 对于全屏滚动的Reels,通常只有一个完全可见的视频,所以取数组内的索引即可
    this.setState({ currentVisibleIndices: visibleIndices });
  };

  rowRenderer = (type, data, index) => {
    switch (type) {
      case 'NORMAL':
        return (
          <View>
            {/* 传递当前视频的索引和可见索引列表给VideoPlayer */}
            <VideoPlayer 
              source={data.item.uri} 
              index={index} 
              visibleIndices={this.state.currentVisibleIndices} 
            />
          </View>
        );
    }
  };

  render() {
    return (
      <>
        <SafeAreaView style={styles.container}>
          <RecyclerListView
            style={styles.videolistcontainer}
            rowRenderer={this.rowRenderer}
            dataProvider={this.dataProvider}
            layoutProvider={this.layoutProvider}
            initialOffset={1}
            pagingEnabled={true}
            showsVerticalScrollIndicator={false}
            // 添加可见行变化的监听
            onVisibleIndicesChanged={this.handleVisibleIndicesChanged}
          />
        </SafeAreaView>
      </>
    );
  }
}
const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', backgroundColor: '#14171A', },
  videolistcontainer: { flex: 1, width: '100%' }, // 补充样式,让列表占满屏幕
});

步骤2:修改VideoPlayer组件(ViewVideo.js)

接下来,我们需要在VideoPlayer组件中根据传入的可见索引来自动控制播放状态,同时保留用户手动暂停的功能:

import React, { useState, useEffect, useRef } from 'react';
import { TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
import Video from 'react-native-video';

const { width: WP, height: HP } = Dimensions.get('window');

const VideoPlayer = ({ source, index, visibleIndices }) => {
  const videoRef = useRef(null);
  const [userPaused, setUserPaused] = useState(false); // 记录用户手动暂停的状态

  // 监听可见索引变化,自动控制播放
  useEffect(() => {
    const isVisible = visibleIndices.includes(index);
    if (videoRef.current) {
      if (isVisible && !userPaused) {
        // 可见且用户未手动暂停,自动播放
        videoRef.current.play();
      } else {
        // 不可见或用户手动暂停,暂停视频
        videoRef.current.pause();
      }
    }
  }, [visibleIndices, index, userPaused]);

  // 组件卸载时暂停视频,避免内存泄漏
  useEffect(() => {
    return () => {
      if (videoRef.current) {
        videoRef.current.pause();
      }
    };
  }, []);

  // 处理用户点击播放/暂停
  const handleTogglePlay = () => {
    setUserPaused(!userPaused);
    if (userPaused) {
      videoRef.current.play();
    } else {
      videoRef.current.pause();
    }
  };

  return (
    <TouchableOpacity 
      style={{ height: HP, width: WP }} 
      onPress={handleTogglePlay}
    >
      <Video
        ref={videoRef}
        source={source}
        style={styles.backgroundVideo}
        resizeMode={'cover'}
        onError={(error) => console.log('Video error:', error)}
        onLoad={() => console.log('Video loaded')}
        onProgress={(progress) => console.log('Video progress:', progress)}
        onEnd={() => {
          // 视频播放结束后重置用户暂停状态,下次进入可见范围自动播放
          setUserPaused(false);
        }}
        repeat={false}
        rate={1.0}
        volume={1.0}
        muted={false}
      />
    </TouchableOpacity>
  );
};

export default VideoPlayer;

const styles = StyleSheet.create({
  backgroundVideo: {
    position: 'absolute',
    width: WP,
    height: HP,
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
  },
});

关键细节说明

  1. 可见行监听:通过onVisibleIndicesChanged获取当前可见的索引,因为我们设置了pagingEnabled={true},所以每次滚动只会有一个视频完全可见,visibleIndices数组里只会有一个索引。
  2. 状态分离:把用户手动暂停的状态(userPaused)和自动播放的逻辑分开,这样既实现了自动播放/暂停,又不会覆盖用户的手动操作。
  3. 内存泄漏处理:在组件卸载时暂停视频,避免视频在后台继续播放占用资源。
  4. 播放结束处理:视频播放结束后重置userPaused状态,这样当用户滚动回来时,视频会自动重新播放。

这样修改后,你就可以实现只有屏幕可见的视频才会播放,滚动出屏幕的视频自动暂停的效果啦!

内容的提问来源于stack exchange,提问作者Hari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 10:02:46