目录
一、视频播放列表(初步实现)
二、更新 视频播放列表(添加全屏播放)代码同步更新
一、视频播放列表(初步实现)
之前没有做个视频的列表播放相关示例,后来自己写了一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()
}
}
效果图如下