Android RecyclerView集成YouTube API滑动播视频黑屏问题
RecyclerView嵌入YouTube播放器滑动黑屏修复方案
核心问题根源
- 异步拉取频道视频ID的任务执行完成后,没有主动通知RecyclerView适配器数据更新,适配器初始持有的是空列表
- 所有播放器实例都直接绑定Activity生命周期,没有适配RecyclerView的条目复用逻辑,离屏播放器未被正确暂停/重置,新条目播放器初始化时序错乱
- 未配置单页滑动吸附效果,滑动结束后无法精准定位到单个全屏条目,自动播放逻辑无有效触发时机
- ViewHolder复用时旧的播放器监听器未移除,
onReady回调可能拿到上一个条目的视频ID,导致加载异常 - 不需要继承
YouTubeBaseActivity,这是谷歌官方旧播放器库的要求,和你用的第三方android-youtube-player库无关,反而可能引发资源冲突
分步修复代码
1. 改造YouTubeActivity
把适配器提成成员变量,异步任务拿到数据后主动刷新;添加PagerSnapHelper实现单页滑动吸附;添加滑动监听,滑动停止后自动播放当前完全可见的视频,暂停其他非可见视频。
public class YouTubeActivity extends AppCompatActivity { private RecyclerView recyclerView; private String apiKey; private String playListID; private ArrayList<String> channelVideoIDs; private RecyclerViewAdapter recyclerViewAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_you_tube); playListID = getString(R.string.youtube_playlist_id); apiKey = getString(R.string.youtube_api_key); channelVideoIDs = new ArrayList<>(); initViews(); new GetPlayListVideosTask().execute(); } private void initViews(){ recyclerView = findViewById(R.id.youtube_recyclerview); recyclerView.setHasFixedSize(true); LinearLayoutManager layoutManager = new LinearLayoutManager(YouTubeActivity.this, RecyclerView.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); // 添加单页吸附效果,实现整页滑动定位 PagerSnapHelper pagerSnapHelper = new PagerSnapHelper(); pagerSnapHelper.attachToRecyclerView(recyclerView); recyclerViewAdapter = new RecyclerViewAdapter(channelVideoIDs, this.getLifecycle()); recyclerView.setAdapter(recyclerViewAdapter); // 滑动停止后自动播放当前可见视频 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { int visiblePos = layoutManager.findFirstCompletelyVisibleItemPosition(); if (visiblePos != RecyclerView.NO_POSITION) { recyclerViewAdapter.playVideoAtPosition(visiblePos); } } } }); } // 异步拉取视频ID任务 private class GetPlayListVideosTask extends AsyncTask<Void, Void, ArrayList<String>> { @Override protected ArrayList<String> doInBackground(Void... voids) { // 替换为你原有拉取视频ID的逻辑 return fetchVideoIdList(); } @Override protected void onPostExecute(ArrayList<String> videoIds) { super.onPostExecute(videoIds); channelVideoIDs.clear(); channelVideoIDs.addAll(videoIds); recyclerViewAdapter.notifyDataSetChanged(); // 数据加载完成后自动播放第一个视频 recyclerView.post(() -> recyclerViewAdapter.playVideoAtPosition(0)); } } }
2. 改造RecyclerViewAdapter
修复复用时的监听器残留问题,添加指定位置播放/全局暂停的方法,回收条目时主动释放播放器资源避免黑屏。
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private ArrayList<String> videoIDList; private Lifecycle lifecycle; private int currentPlayingPos = -1; private WeakReference<RecyclerView> recyclerViewRef; public RecyclerViewAdapter(ArrayList<String> videoIds, Lifecycle lifecycle) { this.videoIDList = videoIds; this.lifecycle = lifecycle; } @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); recyclerViewRef = new WeakReference<>(recyclerView); } @NonNull @Override public RecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View v = inflater.inflate(R.layout.single_video_layout, parent, false); YouTubePlayerView youTubePlayerView = v.findViewById(R.id.youTubePlayerView); lifecycle.addObserver(youTubePlayerView); return new ViewHolder(youTubePlayerView); } @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { // 绑定时预加载视频,不自动播放 viewHolder.cueVideo(videoIDList.get(position)); } @Override public void onViewRecycled(@NonNull ViewHolder holder) { super.onViewRecycled(holder); // 条目回收时暂停播放,避免黑屏和声音泄漏 holder.pause(); } @Override public int getItemCount() { return videoIDList.size(); } // 播放指定位置视频,暂停其他所有视频 public void playVideoAtPosition(int position) { RecyclerView recyclerView = recyclerViewRef.get(); if (recyclerView == null || currentPlayingPos == position) return; currentPlayingPos = position; for (int i = 0; i < getItemCount(); i++) { RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(i); if (holder instanceof ViewHolder) { if (i == position) { ((ViewHolder) holder).play(); } else { ((ViewHolder) holder).pause(); } } } } static class ViewHolder extends RecyclerView.ViewHolder { private YouTubePlayerView youTubePlayerView; private YouTubePlayer youTubePlayer; private String currentVideoId; private AbstractYouTubePlayerListener playerListener; ViewHolder(YouTubePlayerView playerView) { super(playerView); youTubePlayerView = playerView; // 单例化监听器,避免重复添加导致回调错乱 playerListener = new AbstractYouTubePlayerListener() { @Override public void onReady(@NonNull YouTubePlayer initializedYouTubePlayer) { youTubePlayer = initializedYouTubePlayer; youTubePlayer.cueVideo(currentVideoId, 0); } }; youTubePlayerView.addYouTubePlayerListener(playerListener); } // 预加载视频不自动播放 void cueVideo(String videoId) { currentVideoId = videoId; if(youTubePlayer != null) { youTubePlayer.cueVideo(videoId, 0); } } // 播放当前视频 void play() { if (youTubePlayer != null && currentVideoId != null) { youTubePlayer.play(); } } // 暂停当前视频 void pause() { if (youTubePlayer != null) { youTubePlayer.pause(); } } } }
验证注意事项
- 依赖使用最新稳定版的android-youtube-player,测试请用真机,模拟器WebView兼容问题经常导致黑屏
- 确保YouTube API密钥已经开启YouTube Data API v3权限,且控制台配置的包名、签名SHA1和当前测试包一致
- 不需要给播放器加额外的父布局,item布局直接放
YouTubePlayerView占满全屏即可
内容的提问来源于stack exchange,提问作者Wasif Hamdani
相关产品推荐
相关产品推荐

