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

Android聊天问答APP:RecyclerView嵌套视频展示及Firebase数据加载问题

问题解决指南:聊天问答应用中关联视频展示与Firebase视频播放方案

你的代码里存在引用传递导致的列表清空问题,这是getVideoLink()返回null/空列表的核心原因。具体来说:

  1. 复用同一个videoArrayList对象,设置给question后立即调用clear()——由于Java是引用传递,question持有的列表会和videoArrayList指向同一内存地址,clear操作会同时清空question里的视频列表。
  2. 没有为每个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:23:57