热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

详解Android视频滚动列表(偷懒型)

小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧本篇文章主要介绍了Android视频滚动列表(偷懒型),

公司的项目需要一个视频的滚动列表。

搜了些文章比较常见的是根据列表项的可视百分比来判断的。实现起来略复杂。

这里想了一个在要求不高的情况下,实现相对简便的方法:根据列表滚动时可见的第一个列表项的位置来播放和暂停对应列表项内的视频。

它的效果大致是这样的:

 

以下是它的实现。

首先当然是建立列表。

这部分就直接用ListView吧,列表的具体的实现就不贴了。大致就是长这样的一个列表:

 

接下来就是添加播放器。

这里需要注意的是,在ListView里不能使用我们常用的那种VideoView。基于SurfaceView的VideoView由于没有同步缓冲区,它不能在ListView中正常显示。(显然SurfaceView+MediaPlayer的形式也不太适合了)我们需要基于TextureView的视频播放器。

这里偷个懒,就直接用 PLDroidPlayer这个库中的PLVideoTextureView了

在列表的Adapter中的添加播放器。

Adapter的布局:

<&#63;xml version="1.0" encoding="utf-8"&#63;>

  
    
      
      
    
    
  

Adapter部分代码:

package net.codepig.playerlist.adapers;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.pili.pldroid.player.AVOptions;
import com.pili.pldroid.player.PLMediaPlayer;
import com.pili.pldroid.player.widget.PLVideoTextureView;

import net.codepig.playerlist.R;
import net.codepig.playerlist.beans.VideoInfo;
import net.codepig.playerlist.deviceInfo;

import java.util.List;

/**
 * 视频单元页面
 * Created by QZD on 2017/11/13.
 */

public class PlayerAdapter extends BaseAdapter{
  private Context _context;
  private Activity mainActivity;
  private List myVideoData;
  private LayoutInflater inflater;
  private ViewHolder hodler = null;
  private PLMediaPlayer mPlayer=null;
//  private PLVideoTextureView myVideoView;
  private int _id;
  private String _name;
  private String _url="";

  private final String TAG="LOGCAT";

  public PlayerAdapter(Context context, List data) {
    super();
    _cOntext= context;
    mainActivity=(Activity) context;
    myVideoData = data;
    inflater = LayoutInflater.from(context);
  }

  @Override
  public View getView(final int postion, View convertView, ViewGroup parent) {
    hodler = new ViewHolder();
    cOnvertView= inflater.inflate(R.layout.player_adapter_l, null);
    hodler.videoName_t = convertView.findViewById(R.id.videoName_t);
    hodler.videoTable = convertView.findViewById(R.id.videoTable);
    hodler.myVideoView = convertView.findViewById(R.id.myVideoView);
    convertView.setTag(hodler);

    hodler.videoTable.getLayoutParams().width= deviceInfo._screenWidth;
    hodler.videoTable.getLayoutParams().height=deviceInfo._screenHeight;
//    Log.d(TAG,"screenSize:"+deviceInfo._screenWidth+"-"+deviceInfo._screenHeight);

    VideoInfo _vInfo=myVideoData.get(postion);
    _name=_vInfo.get_name();
    hodler.videoName_t.setText(_vInfo.get_name());
    _id=_vInfo.get_id();
    _url=_vInfo.get_url();
    if(!_url.equals("")) {
      setVideo(_url);
    }

    return convertView;
  }

  /**
   * 初始化播放器
   * @param url
   */
  private void setVideo(String url){
    int codec = mainActivity.getIntent().getIntExtra("mediaCodec", AVOptions.MEDIA_CODEC_AUTO);
    AVOptions optiOns= new AVOptions();
    options.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000);
    options.setInteger(AVOptions.KEY_MEDIACODEC, codec);
    hodler.myVideoView.setAVOptions(options);
    hodler.myVideoView.setVideoPath(url);
    hodler.myVideoView.start();

    hodler.myVideoView.setOnErrorListener(new PLMediaPlayer.OnErrorListener(){
      @Override
      public boolean onError(PLMediaPlayer mp, int errorCode) {
        Log.d(TAG,"errorCode:"+errorCode);
        return true;
      }
    });
    hodler.myVideoView.setOnCompletionListener(new PLMediaPlayer.OnCompletionListener() {
      @Override
      public void onCompletion(PLMediaPlayer mp) {
//        Log.d(TAG, "player onCompletion:"+videoDuration/1000+"-"+_curTime/1000);
        hodler.myVideoView.seekTo(0);
        hodler.myVideoView.start();
      }
    });
    hodler.myVideoView.setOnPreparedListener(new PLMediaPlayer.OnPreparedListener() {
      @Override
      public void onPrepared(PLMediaPlayer mediaPlayer, int percent) {
        Log.d(TAG, "player onPrepared");
        if(mPlayer==null){
          mPlayer=mediaPlayer;
        }
        //播放
        if(hodler.myVideoView!=null){
          hodler.myVideoView.start();
        }else{
          Log.d(TAG, _name+"no myVideoView");
        }
      }
    });
    hodler.myVideoView.setOnBufferingUpdateListener(new PLMediaPlayer.OnBufferingUpdateListener() {
      @Override
      public void onBufferingUpdate(PLMediaPlayer mp, int percent) {
        try {
          int _pec = hodler.myVideoView.getBufferPercentage();//百分比到99就停,进度条会留空
          if (_pec == 99) {
            _pec = 100;
          }
        }catch (Exception e){
          Log.d(TAG,"percentage error:"+e.toString());
        }
      }
    });
    hodler.myVideoView.setOnVideoSizeChangedListener(new PLMediaPlayer.OnVideoSizeChangedListener() {
      @Override
      public void onVideoSizeChanged(PLMediaPlayer plMediaPlayer, int width, int height) {
        Log.d(TAG,"VideoSize:"+width+"_"+height);
      }
    });
  }

  @Override
  public int getCount() {
    if (myVideoData != null) {
      return myVideoData.size();
    } else {
      return 0;
    }
  }

  @Override
  public Object getItem(int position) {
    return myVideoData.get(position);
  }

  @Override
  public long getItemId(int postion) {
    // TODO Auto-generated method stub
    return postion;
  }

  public static class ViewHolder {
    public TextView videoName_t;
    public RelativeLayout videoTable;
    public PLVideoTextureView myVideoView;
  }
}

添加完播放器大致长这样:

 

接下来就是重点了,要根据列表的滚动来播放和暂停视频。

这里根据当前滚动的位置来进行判断。

首先添加滚动监听:

    myVideoList.setAdapter(playerAdapter);
    myVideoList.setOnScrollListener(new AbsListView.OnScrollListener() {
      @Override
      public void onScrollStateChanged(AbsListView view, int scrollState) {
//        Log.d(TAG,"onScrollStateChanged:"+scrollState);
        //SCROLL_STATE_FLING = 滚动中;SCROLL_STATE_IDLE = 结束滚动;SCROLL_STATE_TOUCH_SCROLL = 开始滚动;
        if(scrollState==SCROLL_STATE_IDLE){
          Log.d(TAG,"FirstVisiblePosition:"+myVideoList.getFirstVisiblePosition());
          View v0=myVideoList.getChildAt(0);
          if(v0!=null){
            int scrollTop=v0.getTop();
            Log.d(TAG,"scroll top:"+scrollTop);
          }
        }
      }

      @Override
      public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      }
    });

这里通过getFirstVisiblePosition()获得可见的第一个元素,并使用getTop()获得该元素的偏移量。

接下来增加对元素内视频的操作,这里通过更新列表的数据来实现。

修改一下上面的监听,判断当前第二个可见item的位置,当到达指定位置时将播放标识置为true。原先播放中的item的播放标识置为false。
然后更新数据。

     myVideoList.setOnScrollListener(new AbsListView.OnScrollListener() {
      @Override
      public void onScrollStateChanged(AbsListView view, int scrollState) {
        //SCROLL_STATE_FLING = 滚动中;SCROLL_STATE_IDLE = 结束滚动;SCROLL_STATE_TOUCH_SCROLL = 开始滚动;
        if(scrollState==SCROLL_STATE_IDLE){
          int _index=myVideoList.getFirstVisiblePosition()+1;
          View v1=myVideoList.getChildAt(1);//取可见元素的第二个
          if(v1!=null){
            int scrollTop=v1.getTop();
            if(scrollTop<200){
              if(_oldItem!=_index) {
                _infoList.get(_index).set_playing(true);
                _infoList.get(_oldItem).set_playing(false);
                _oldItem=_index;
                playerAdapter.notifyDataSetChanged();
              }
            }
//            Log.d(TAG,"scroll top:"+scrollTop);
          }
        }
      }

      @Override
      public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      }
    });

这个的位置判断上直接写死了200像素,作为一个DEMO,位置判断的数值上不是很讲究。这个其实应该根据滚动方向和item的高度来计算的。

在Adapter的getView()方法中根据_playing的状态播放或停止视频:(停止的时候要记得释放掉播放器资源哦,不然列表中这么多视频的内存占用是很可怕的哦。)

  @Override
  public View getView(final int postion, View convertView, ViewGroup parent) {
    hodler = new ViewHolder();
    cOnvertView= inflater.inflate(R.layout.player_adapter_l, null);
    hodler.videoName_t = convertView.findViewById(R.id.videoName_t);
    hodler.videoTable = convertView.findViewById(R.id.videoTable);
    hodler.myVideoView = convertView.findViewById(R.id.myVideoView);
    convertView.setTag(hodler);

    hodler.videoTable.getLayoutParams().width= deviceInfo._screenWidth;
    hodler.videoTable.getLayoutParams().height=deviceInfo._screenHeight;
    Log.d(TAG,"screenSize:"+deviceInfo._screenWidth+"-"+deviceInfo._screenHeight);

    VideoInfo _vInfo=myVideoData.get(postion);
    _name=_vInfo.get_name();
    hodler.videoName_t.setText(_vInfo.get_name());
    _id=_vInfo.get_id();
    _url=_vInfo.get_url();
    if(!_url.equals("")) {
    //视频的播放和停止
      if(_vInfo.get_playing()){
        setVideo(_url);
      }else{
        if(hodler.myVideoView!=null) {
          if (hodler.myVideoView.isPlaying()) {
            hodler.myVideoView.stopPlayback();
            hodler.myVideoView.releaseSurfactexture();
          }
        }
      }
    }
    return convertView;
  }

嗯,完工。

改天再整列表的可视百分比判断。

相关github项目地址:https://github.com/codeqian/playerlist

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 2023年1月28日网络安全热点
    涵盖最新的网络安全动态,包括OpenSSH和WordPress的安全更新、VirtualBox提权漏洞、以及谷歌推出的新证书验证机制等内容。 ... [详细]
  • Docker基础入门与环境配置指南
    本文介绍了Docker——一款用Go语言编写的开源应用程序容器引擎。通过Docker,用户能够将应用及其依赖打包进容器内,实现高效、轻量级的虚拟化。容器之间采用沙箱机制,确保彼此隔离且资源消耗低。 ... [详细]
  • 本文介绍了如何利用 Apache NiFi 的灵活性和扩展性,通过自定义组件来解决标准组件无法满足的特定业务需求。文章不仅涵盖了自定义处理器的基本步骤,还讨论了调试自定义组件时可能遇到的问题及解决方案。 ... [详细]
  • selenium通过JS语法操作页面元素
    做过web测试的小伙伴们都知道,web元素现在很多是JS写的,那么既然是JS写的,可以通过JS语言去操作页面,来帮助我们操作一些selenium不能覆盖的功能。问题来了我们能否通过 ... [详细]
  • 将XML数据迁移至Oracle Autonomous Data Warehouse (ADW)
    随着Oracle ADW的推出,数据迁移至ADW成为业界关注的焦点。特别是XML和JSON这类结构化数据的迁移需求日益增长。本文将通过一个实际案例,探讨如何高效地将XML数据迁移至ADW。 ... [详细]
  • 本文旨在探讨Swift中的Closure与Objective-C中的Block之间的区别与联系,通过定义、使用方式以及外部变量捕获等方面的比较,帮助开发者更好地理解这两种机制的特点及应用场景。 ... [详细]
  • 实现Win10与Linux服务器的SSH无密码登录
    本文介绍了如何在Windows 10环境下使用Git工具,通过配置SSH密钥对,实现与Linux服务器的无密码登录。主要步骤包括生成本地公钥、上传至服务器以及配置服务器端的信任关系。 ... [详细]
  • 汇总了2023年7月7日最新的网络安全新闻和技术更新,包括最新的漏洞披露、工具发布及安全事件。 ... [详细]
  • 深入理解iOS中的链式编程:以Masonry为例
    本文通过介绍Masonry这一轻量级布局框架,探讨链式编程在iOS开发中的应用。Masonry不仅简化了Auto Layout的使用,还提高了代码的可读性和维护性。 ... [详细]
  • WebBenchmark:强大的Web API性能测试工具
    本文介绍了一款名为WebBenchmark的Web API性能测试工具,该工具不仅支持HTTP和HTTPS服务的测试,还提供了丰富的功能来帮助开发者进行高效的性能评估。 ... [详细]
  • ArcBlock 发布 ABT 节点 1.0.31 版本更新
    2020年11月9日,ArcBlock 区块链基础平台发布了 ABT 节点开发平台的1.0.31版本更新,此次更新带来了多项功能增强与性能优化。 ... [详细]
  • 基于SSM框架的在线考试系统:随机组卷功能详解
    本文深入探讨了基于SSM(Spring, Spring MVC, MyBatis)框架构建的在线考试系统中,随机组卷功能的设计与实现方法。 ... [详细]
  • binlog2sql,你该知道的数据恢复工具
    binlog2sql,你该知道的数据恢复工具 ... [详细]
  • Python3爬虫入门:pyspider的基本使用[python爬虫入门]
    Python学习网有大量免费的Python入门教程,欢迎大家来学习。本文主要通过爬取去哪儿网的旅游攻略来给大家介绍pyspid ... [详细]
  • 在Android中实现黑客帝国风格的数字雨效果
    本文将详细介绍如何在Android平台上利用自定义View实现类似《黑客帝国》中的数字雨效果。通过实例代码,我们将探讨如何设置文字颜色、大小,以及如何控制数字下落的速度和间隔。 ... [详细]
author-avatar
Jesus_kk
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有