Android聊天问答APP:RecyclerView嵌套视频展示及Firebase数据加载问题
问题解决指南:聊天问答应用中关联视频展示与Firebase视频播放方案
一、修复getVideoLink()返回null的代码问题
你的代码里存在引用传递导致的列表清空问题,这是getVideoLink()返回null/空列表的核心原因。具体来说:
- 复用同一个
videoArrayList对象,设置给question后立即调用clear()——由于Java是引用传递,question持有的列表会和videoArrayList指向同一内存地址,clear操作会同时清空question里的视频列表。 - 没有为每个Question单独创建视频列表,可能导致多个Question共享同一列表的错误。
修复后的代码如下:
reference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { for (DataSnapshot postSnap : dataSnapshot.getChildren()) { Question question = postSnap.getValue(Question.class); if (question.getStatus() == 1) { // 为当前Question创建独立的视频列表,避免引用冲突 List<QuestionVideo> currentVideoList = new ArrayList<>(); for (DataSnapshot snap : postSnap.child("VideoLink").getChildren()) { QuestionVideo video = snap.getValue(QuestionVideo.class); if (video != null) { currentVideoList.add(video); Log.d("HELLOOO", "video is not null: " + video.toString()); } } // 直接设置当前Question的视频列表,无需清空 question.setVideoLink(currentVideoList); Log.d("HELLOOO", "getting list: " + question.getVideoLink()); // 添加审核通过的问题到列表 list.add(question); } } // 记得最后通知RecyclerView数据更新 yourAdapter.notifyDataSetChanged(); } @Override public void onCancelled(DatabaseError databaseError) { // 处理数据库读取失败的情况 Log.e("FirebaseError", "Data read failed", databaseError.toException()); } });
同时请确保Question类的字段与setter/getter正确实现:
public class Question { // 其他字段... private List<QuestionVideo> videoLink; // 空构造函数(Firebase序列化必需) public Question() {} public void setVideoLink(List<QuestionVideo> videoLink) { this.videoLink = videoLink; } public List<QuestionVideo> getVideoLink() { return videoLink; } }
二、实现问题下方的视频列表展示(RecyclerView嵌套)
要在每个问题下方展示关联视频,推荐使用RecyclerView嵌套方案:主RecyclerView的每个item中包含一个横向子RecyclerView,用于展示视频卡片。
1. 主Item布局(question_item.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <!-- 问题内容区域 --> <TextView android:id="@+id/tv_question" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold"/> <TextView android:id="@+id/tv_sender" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/darker_gray" android:layout_marginTop="4dp"/> <!-- 视频列表区域:默认隐藏,有视频时显示 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_videos" android:layout_width="match_parent" android:layout_height="120dp" android:layout_marginTop="12dp" android:visibility="gone"/> </LinearLayout>
2. 视频Item布局(video_item.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="180dp" android:layout_height="match_parent" android:orientation="vertical" android:padding="8dp" android:layout_marginEnd="8dp"> <!-- 视频缩略图 --> <ImageView android:id="@+id/iv_video_thumbnail" android:layout_width="match_parent" android:layout_height="80dp" android:scaleType="centerCrop" android:background="@android:color/darker_gray"/> <!-- 视频信息 --> <TextView android:id="@+id/tv_video_duration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" android:textColor="@android:color/white" android:background="@android:color/black" android:padding="2dp" android:layout_marginTop="4dp"/> <TextView android:id="@+id/tv_sender_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="12sp" android:textColor="@android:color/darker_gray" android:layout_marginTop="4dp"/> </LinearLayout>
3. 主RecyclerView适配器改造
在主适配器的onBindViewHolder中处理视频列表的显示逻辑,并为子RecyclerView设置适配器:
public class QuestionAdapter extends RecyclerView.Adapter<QuestionAdapter.QuestionViewHolder> { private List<Question> questionList; private Context context; public QuestionAdapter(List<Question> questionList, Context context) { this.questionList = questionList; this.context = context; } @NonNull @Override public QuestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.question_item, parent, false); return new QuestionViewHolder(view); } @Override public void onBindViewHolder(@NonNull QuestionViewHolder holder, int position) { Question question = questionList.get(position); holder.tvQuestion.setText(question.getQuestion()); holder.tvSender.setText("By: " + question.getSenderName()); List<QuestionVideo> videos = question.getVideoLink(); if (videos != null && !videos.isEmpty()) { holder.rvVideos.setVisibility(View.VISIBLE); // 设置横向布局管理器 LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); holder.rvVideos.setLayoutManager(layoutManager); // 绑定视频适配器 VideoAdapter videoAdapter = new VideoAdapter(videos, context); holder.rvVideos.setAdapter(videoAdapter); } else { holder.rvVideos.setVisibility(View.GONE); } } @Override public int getItemCount() { return questionList.size(); } public static class QuestionViewHolder extends RecyclerView.ViewHolder { TextView tvQuestion, tvSender; RecyclerView rvVideos; public QuestionViewHolder(@NonNull View itemView) { super(itemView); tvQuestion = itemView.findViewById(R.id.tv_question); tvSender = itemView.findViewById(R.id.tv_sender); rvVideos = itemView.findViewById(R.id.rv_videos); } } }
4. 视频RecyclerView适配器
public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHolder> { private List<QuestionVideo> videoList; private Context context; public VideoAdapter(List<QuestionVideo> videoList, Context context) { this.videoList = videoList; this.context = context; } @NonNull @Override public VideoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.video_item, parent, false); return new VideoViewHolder(view); } @Override public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) { QuestionVideo video = videoList.get(position); holder.tvDuration.setText(video.getDuration()); holder.tvSenderName.setText(video.getVideoSenderName()); // 用Glide加载视频第一帧作为缩略图 Glide.with(context) .asBitmap() .load(video.getDownload_url()) .into(holder.ivThumbnail); // 点击缩略图触发播放 holder.itemView.setOnClickListener(v -> startVideoPlayback(video.getDownload_url())); } private void startVideoPlayback(String videoUrl) { // 后续跳转播放页面 Intent intent = new Intent(context, VideoPlayerActivity.class); intent.putExtra("VIDEO_URL", videoUrl); context.startActivity(intent); } @Override public int getItemCount() { return videoList.size(); } public static class VideoViewHolder extends RecyclerView.ViewHolder { ImageView ivThumbnail; TextView tvDuration, tvSenderName; public VideoViewHolder(@NonNull View itemView) { super(itemView); ivThumbnail = itemView.findViewById(R.id.iv_video_thumbnail); tvDuration = itemView.findViewById(R.id.tv_video_duration); tvSenderName = itemView.findViewById(R.id.tv_sender_name); } } }
三、Firebase视频下载URL的最佳播放方案
对于Firebase Storage返回的HTTP视频URL,推荐使用Google ExoPlayer作为播放组件,它比系统MediaPlayer有更好的兼容性、性能和功能扩展性。
1. 集成ExoPlayer
在app级build.gradle中添加依赖:
implementation "com.google.android.exoplayer:exoplayer-core:2.19.1" implementation "com.google.android.exoplayer:exoplayer-ui:2.19.1"
2. 实现视频播放页面(VideoPlayerActivity)
布局文件(activity_video_player.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.google.android.exoplayer2.ui.StyledPlayerView android:id="@+id/player_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
Activity代码
public class VideoPlayerActivity extends AppCompatActivity { private StyledPlayerView playerView; private SimpleExoPlayer player; private String videoUrl; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_player); videoUrl = getIntent().getStringExtra("VIDEO_URL"); playerView = findViewById(R.id.player_view); initPlayer(); } private void initPlayer() { // 创建ExoPlayer实例 player = new SimpleExoPlayer.Builder(this).build(); playerView.setPlayer(player); // 加载并播放视频 MediaItem mediaItem = MediaItem.fromUri(videoUrl); player.setMediaItem(mediaItem); player.prepare(); player.play(); } @Override protected void onPause() { super.onPause(); player.pause(); } @Override protected void onStop() { super.onStop(); player.stop(); } @Override protected void onDestroy() { super.onDestroy(); player.release(); } }
额外优化建议
- 缩略图优化:上传视频时同步生成并存储缩略图到Firebase Storage,直接加载缩略图URL比获取视频第一帧更快。
- 性能优化:为嵌套RecyclerView设置
setRecycledViewPool复用视图池;主适配器中缓存视频适配器,避免重复创建实例。 - 错误处理:添加视频加载、播放的错误监听,处理网络异常、视频无法加载的情况。
内容的提问来源于stack exchange,提问作者Arpit Gupta
相关产品推荐
相关产品推荐

