android 原生开发视频列表播放

目录

一、视频播放列表(初步实现)

二、更新  视频播放列表(添加全屏播放)代码同步更新


一、视频播放列表(初步实现)

之前没有做个视频的列表播放相关示例,后来自己写了一demo,每个item放一个播放器,虽然觉得不合理,一直也没去管它,后来根据学习flutter的经验参考,发现可以使用 RecyclerView 根据类型加载不同的布局来解决这个问题。在此记录以作笔记。项目示例的细节未作处理。

首先设置两个布局,一个加载图片,一个加载播放器

layout_image 封面图片

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" >

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:paddingTop="5dp"
            android:paddingBottom="5dp">

        <com.main.ersiba.util.YJImageView
                android:id="@+id/iv_header"
                android:layout_width="30dp"
                android:layout_height="30dp"/>

        <TextView
                android:id="@+id/tv_author"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:textColor="#000"/>

    </LinearLayout>

    <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#ffffff"/>

    <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:background="@drawable/bg_video">

        <ImageView
                android:id="@+id/iv_video_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="center"/>

        <ImageView
                android:id="@+id/iv_play_state"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_centerInParent="true"
                android:src="@drawable/ic_play_pause"/>

    </RelativeLayout>

</LinearLayout>

layout_video 播放器

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:paddingTop="5dp"
            android:paddingBottom="5dp">

        <com.main.ersiba.util.YJImageView
                android:id="@+id/iv_header"
                android:layout_width="30dp"
                android:layout_height="30dp"/>

        <TextView
                android:id="@+id/tv_author"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:textColor="#000"/>

    </LinearLayout>

    <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#ffffff"/>


        <com.tencent.liteav.demo.play.SuperPlayerView
                android:id="@+id/vv_play"
                android:layout_width="match_parent"
                android:layout_height="180dp"
                android:background="#000"/>

    <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#33333333"/>
</LinearLayout>

RecyclerView 根据类型加载不同的布局

package com.main.ersiba.util;

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.VideoView;
import com.bumptech.glide.Glide;
import com.main.ersiba.R;
import com.main.ersiba.VideoInfo;
import com.socks.library.KLog;
import com.tencent.liteav.demo.play.SuperPlayerModel;
import com.tencent.liteav.demo.play.SuperPlayerView;

import java.util.List;

/**
 * 作者: 吴昶 .
 * 时间: 2019/4/26 10:48
 * 功能简介:
 */
public class RecyAdapter extends RecyclerView.Adapter<RecyAdapter.RecyHolder> {

    private final int TYPE_IMAGE=0;
    private final int TYPE_VIDEO=1;

    int playIndex=-1;//选中的播放序号
    Context context;
    List<VideoInfo.Godsode> datas;

    public RecyAdapter(Context context){
        this.context=context;
    }

    public void upData(List<VideoInfo.Godsode> datas){
        this.datas=datas;
        notifyDataSetChanged();
    }

    public void setPlayIndex(int id){
        this.playIndex=id;
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        //根据序号决定加载布局类型
        if(playIndex==position){
            return TYPE_VIDEO;
        }else {
            return TYPE_IMAGE;
        }
    }

    private void initData(@NonNull RecyHolder holder, final int position, final VideoInfo.Godsode item){
        holder.getView(TextView.class,R.id.tv_author).setText(item.getUsername());
        Glide.with(context).load(item.getHeader()).into(holder.getView(ImageView.class,R.id.iv_header));
        //判断播放的 item 的序号
        if(playIndex==position){
            SuperPlayerView videoView=holder.getView(SuperPlayerView.class,R.id.vv_play);
            SuperPlayerModel superPlayerModel=new SuperPlayerModel();
            superPlayerModel.url=item.getVideo();
            videoView.playWithModel(superPlayerModel);
        }else {
            Glide.with(context).load(item.getThumbnail()).into(holder.getView(ImageView.class, R.id.iv_video_bg));

            holder.getView(ImageView.class, R.id.iv_play_state).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //点击播放按钮时设置播放的 item 序号
                    setPlayIndex(position);
                }
            });
        }
    }

    /**
     * 根据布局类型加载布局文件
     * @param viewType
     * @return
     */
    private int getViewType(int viewType){
        switch (viewType){
            case TYPE_IMAGE:
                return R.layout.layout_image;
            case TYPE_VIDEO:
                return R.layout.layout_video;
            default:
                return R.layout.layout_image;
        }
    }

    @NonNull
    @Override
    public RecyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return RecyHolder.getHolder(context,parent,getViewType(viewType));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyHolder holder, int position) {
        initData(holder,position,datas.get(position));
    }

    @Override
    public int getItemCount() {
        return datas==null?0:datas.size();
    }

    /**
     * 适配器 holder
     */
    static class RecyHolder extends RecyclerView.ViewHolder{

        SparseArray<View> viewSparseArray;
        View currentView;

        private RecyHolder(View itemView) {
            super(itemView);
            viewSparseArray=new SparseArray<View>();
            this.currentView=itemView;
        }

        static RecyHolder getHolder(Context context,ViewGroup parent,int type){
            View view= LayoutInflater.from(context).inflate(type,parent,false);
            return new RecyHolder(view);
        }

        <T extends View> T getView(Class<T> t,int viewId){
            View view=viewSparseArray.get(viewId);
            if(view==null){
                view=currentView.findViewById(viewId);
            }
            return (T) view;
        }

    }
}

这样每次播放的时候就只会加载一个播放器了,本文提供一个解决思路,未作细节处理,以后完善再更新到代码中。

二、更新  视频播放列表(添加全屏播放)代码同步更新

在添加全屏部分时为了达到想要的效果,我把播放器提取出来了,不再放到布局文件中,在布局文件中只保留Linearlayout作为播放器的容器,在需要的时候把播放器作为一个子view加入容器中,不需要的时候在容器中移除,这样对于所有需要播放的位置都可以使用同一个播放器。

首先提取播放器到 VsSuperPlay 中(这个类的名称取的比较随意,根据自己审美修改)

public class VsSuperPlay {

    private SuperPlayerView superPlayerView;
    private static VsSuperPlay vsSuperPlay;

    public static synchronized VsSuperPlay getInstance(Context context){
        if(vsSuperPlay==null){
            vsSuperPlay=new VsSuperPlay(context);
        }
        return vsSuperPlay;
    }

    /**
     * 初始化播放器
     * @param context
     */
    private VsSuperPlay(Context context){
        superPlayerView=new SuperPlayerView(context);
    }

    public void initUrl(String url,String title){
        SuperPlayerModel superPlayerModel=new SuperPlayerModel();
        superPlayerModel.url=url;
        superPlayerModel.title=title;
        superPlayerView.playWithModel(superPlayerModel);
    }

    public SuperPlayerView getSuperPlayerView(){
        return superPlayerView;
    }

    public void onPause(){
        if(superPlayerView!=null){
            superPlayerView.onPause();
        }
    }

    public void stopPlay(){
        if(superPlayerView!=null){
            superPlayerView.onPause();
            superPlayerView.release();
            superPlayerView=null;
            vsSuperPlay=null;
        }
    }

}

修改 布局文件 layout_video 为

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:paddingTop="5dp"
            android:paddingBottom="5dp">

        <com.main.ersiba.util.YJImageView
                android:id="@+id/iv_header"
                android:layout_width="30dp"
                android:layout_height="30dp"/>

        <TextView
                android:id="@+id/tv_author"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:textColor="#000"/>

    </LinearLayout>

    <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#ffffff"/>

    <!-- 播放器容器 -->
    <LinearLayout
            android:id="@+id/linear_play"
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:orientation="vertical"
            android:background="#000">

    </LinearLayout>

    <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#33333333"/>
</LinearLayout>

同时为主布局文件添加用于展示全屏的容器 layout_main为

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 列表 -->
        <android.support.v7.widget.RecyclerView
                android:id="@+id/recy_video"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

        <!-- 全屏播放容器 -->
        <LinearLayout
                android:visibility="gone"
                android:id="@+id/linear_big_play"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#000"
                android:orientation="vertical">

        </LinearLayout>


</RelativeLayout>

修改 RecyclerView 的适配器文件

public class RecyAdapter extends RecyclerView.Adapter<RecyHolder> {

    private final int TYPE_IMAGE=0;
    private final int TYPE_VIDEO=1;

    private int playIndex=-1;//选中的播放序号
    private Context context;
    private List<VideoInfo.Godsode> datas;
    private SuperPlayerView videoView;
    private LinearLayout playBody;

    public RecyAdapter(Context context){
        this.context=context;
    }

    public void upData(List<VideoInfo.Godsode> datas){
        this.datas=datas;
        notifyDataSetChanged();
    }

    private void setPlayIndex(int id){
        this.playIndex=id;
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        //根据序号决定加载布局类型
        if(playIndex==position){
            return TYPE_VIDEO;
        }else {
            return TYPE_IMAGE;
        }
    }

    private void initData(@NonNull RecyHolder holder, final int position, final VideoInfo.Godsode item){
        holder.getView(TextView.class,R.id.tv_author).setText(item.getUsername());
        Glide.with(context).load(item.getHeader()).into(holder.getView(ImageView.class,R.id.iv_header));
        //判断播放的 item 的序号
        if(playIndex==position){
            playBody=holder.getView(LinearLayout.class,R.id.linear_play);
            //此处获取的播放器只会在第一次播放的时候创建,然后使用的就不会再重新创建
            videoView=VsSuperPlay.getInstance(context).getSuperPlayerView();
            VsSuperPlay.getInstance(context).initUrl(item.getVideo(),item.getText());
            playBody.addView(videoView);
        }else {
            Glide.with(context).load(item.getThumbnail()).into(holder.getView(ImageView.class, R.id.iv_video_bg));

            holder.getView(ImageView.class, R.id.iv_play_state).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //设置新的播放内容时,先移除SuperPlayerView
                    if(playBody!=null){
                        playBody.removeAllViews();
                    }
                    //点击播放按钮时设置播放的 item 序号
                    setPlayIndex(position);
                }
            });
        }
    }

    /**
     * 根据布局类型加载布局文件
     * @param viewType
     * @return
     */
    private int getViewType(int viewType){
        switch (viewType){
            case TYPE_IMAGE:
                return R.layout.layout_image;
            case TYPE_VIDEO:
                return R.layout.layout_video;
            default:
                return R.layout.layout_image;
        }
    }

    @NonNull
    @Override
    public RecyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return RecyHolder.getHolder(context,parent,getViewType(viewType));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyHolder holder, int position) {
        initData(holder,position,datas.get(position));
    }

    @Override
    public int getItemCount() {
        return datas==null?0:datas.size();
    }

    public void removedPlay(){
        if(playBody!=null){
            playBody.removeAllViews();
        }
    }

    public void addPlay(){
        if(playBody!=null){
            playBody.addView(videoView);
        }
    }

}

把 RecyHolder 提取出来作为公用的holder

/**
 * 作者: 吴昶 .
 * 时间: 2019/4/28 14:14
 * 功能简介:RecyclerView 的适配器通用的holder
 */
public class RecyHolder extends RecyclerView.ViewHolder{

    SparseArray<View> viewSparseArray;
    View currentView;

    private RecyHolder(View itemView) {
        super(itemView);
        viewSparseArray=new SparseArray<View>();
        this.currentView=itemView;
    }

    static RecyHolder getHolder(Context context, ViewGroup parent, int type){
        View view= LayoutInflater.from(context).inflate(type,parent,false);
        return new RecyHolder(view);
    }

    <T extends View> T getView(Class<T> t,int viewId){
        View view=viewSparseArray.get(viewId);
        if(view==null){
            view=currentView.findViewById(viewId);
        }
        return (T) view;
    }

}

修改MainActivity的代码对于横竖屏转换时的界面变化设置

class MainActivity : AppCompatActivity() {

    private var recyAdapter:RecyAdapter?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        setContentView(R.layout.activity_main)

        recyAdapter=RecyAdapter(this)
        val layoutManager=LinearLayoutManager(this)
        recy_video.layoutManager=layoutManager
        recy_video.adapter=recyAdapter
        getVideoInfo()
    }


    fun getVideoInfo(){
        val map=HashMap<String,String>()
        map["type"] = "5"
        map["page"] = "1"
        ServerModule.getVideoIndo("5","1",object:RequestListener<VideoInfo>{
            override fun onResquestSuccess(result: VideoInfo?) {
                Log.e("***ss******",result.toString())
                recyAdapter!!.upData(result!!.getData())
            }

            override fun onResquestError(requestCode: Int, errorCode: Int, message: String?) {
                Log.e("***********",message)
            }
        })
    }

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        //横屏时显示大屏
        if(newConfig!!.orientation==Configuration.ORIENTATION_LANDSCAPE){
            //将播放器从列表中移除
            recyAdapter!!.removedPlay()
            //隐藏列表
            recy_video.visibility=View.GONE
            //显示大屏播放容器
            linear_big_play.visibility= View.VISIBLE
            //添加播放器到大屏中
            linear_big_play.addView(VsSuperPlay.getInstance(this).superPlayerView)
        }else if(newConfig.orientation==Configuration.ORIENTATION_PORTRAIT){
            //播放器从大屏中移除
            linear_big_play.removeAllViews()
            //隐藏大屏播放容器
            linear_big_play.visibility= View.GONE
            //添加播放器到列表
            recyAdapter!!.addPlay()
            //显示列表
            recy_video.visibility=View.VISIBLE

        }
    }

    override fun onPause() {
        VsSuperPlay.getInstance(this).onPause()
        super.onPause()
    }

    override fun onDestroy() {
        //界面退出时终止播放
        VsSuperPlay.getInstance(this).stopPlay()
        super.onDestroy()
    }
}

 

效果图如下

 

代码下载

码云中国GIT

本文中的播放器时腾讯的超级播放器

 

 

 

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值