使用VideoView自定义一个播放器控件
介绍
最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了。在写VideoView播放视频时候定义控制的代码全写在Actvity里了,写完一看我靠代码好乱,于是就写了个自定义的播放器控件,支持指定大小,可以横竖屏切换,手动左右滑动快进快退。好了,下面开始。
效果图
效果图有点卡,我也不知道为啥。。。。。
VideoView介绍
这个是我们实现视频播放最主要的控件,详细的介绍大家百度就去看,这里介绍几个常用的方法。
用于播放视频文件。 VideoView 类可以从不同的来源(例如资源文件或内容提供器) 读取图像,计算和维护视频的画面尺寸以使其适用于任何布局管理器, 并提供一些诸如缩放、着色之类的显示选项。
VideoView 常用的几个方法
public int getDuration ()
获得所播放视频的总时间
public int getCurrentPosition ()
获得当前的位置,我们可以用来设置播放时间的显示
public int getCurrentPosition ()
获得当前的位置,我们可以用来设置播放时间的显示
public int pause ()
暂停播放
public int seekTo ()
设置播放位置,我们用来总快进的时候就能用到
public int setOnCompletionListener(MediaPlayer.OnCompletionListener l)
注册在媒体文件播放完毕时调用的回调函数。
public int setOnErrorListener (MediaPlayer.OnErrorListener l)
注册在设置或播放过程中发生错误时调用的回调函数。如果未指定回调函数, 或回调函数返回false,会弹一个dialog提示用户不能播放
public void setOnPreparedListener (MediaPlayer.OnPreparedListener l)
注册在媒体文件加载完毕,可以播放时调用的回调函数。
public void setVideoURI (Uri uri)
设置播放的视频源,也可以用setVideoPath指定本地文件
public void start ()
开始播放
getHolder().setFixedSize(width,height);
设置VideoView的分辨率,如果我们的VideoView在开始播放的时候是竖屏的,当横屏的时候我们改变了VideoView的布局大小,就需要这个方法重新设置它的分辨率,否则你会发现改变了之后VideoView内部的视频部分还是原来的大小,这点要注意。
自定义播放器思路
说是自定义,其实无非就是把这些VideoView和用来显示的其它控件结合在一起,然后在内部处理它的事件交互,我们要做的就是以下几步:1、写好整个空间的布局。2、在自定义控件的内部获取到整个控件内部的各个小控件,并且为它们设置一些初始化事件。3、根据你自己的逻辑和想实现的效果在里面写自己的事件处理,需要在和外部进行交互就提供方法和接口咯。最后就是使用测试效果了。好了,我们就跟着这里说的4步去实现吧!
具体实现
1、第一步,写自己的布局文件
想要的效果就是在底部放一个状态栏显示时间等信息,播放进度,进入全屏,中间放一个快进快退的状态,布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants">
<com.qiangyu.test.commonvideoview.MyVideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
//底部状态栏
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#CC282828"
android:padding="3dip"
android:id="@+id/videoControllerLayout"
android:gravity="center"
android:layout_gravity="bottom">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:id="@+id/videoPauseBtn"
android:paddingRight="10dip"
android:paddingLeft="10dp">
<ImageView android:layout_width="22dp"
android:layout_height="22dp"
android:id="@+id/videoPauseImg" />
</LinearLayout>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingRight="0dip">
<SeekBar android:layout_width="fill_parent"
android:id="@+id/videoSeekBar"
android:layout_weight="1"
style="@android:style/Widget.Holo.SeekBar"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center"
android:text="00:00"
android:textSize="12dp"
android:id="@+id/videoCurTime"
android:textColor="#FFF"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="12dp"
android:textColor="#FFF"
android:text="/"/>
<TextView android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center"
android:text="00:00"
android:textSize="12dp"
android:id="@+id/videoTotalTime"
android:textColor="#FFF"
android:layout_marginRight="10dp"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/screen_status_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="@+id/screen_status_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/iconfont_enter_32"/>
</LinearLayout>
</LinearLayout>
//VideoVIEW中间的开始按钮和进度条以及快进快退的提示
<ProgressBar android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/progressBar"
style="@android:style/Widget.Holo.ProgressBar.Small"/>
<ImageView android:layout_width="30dip"
android:layout_height="30dip"
android:id="@+id/videoPlayImg"
android:layout_gravity="center"
android:src="@mipmap/video_box_play"/>
<LinearLayout
android:id="@+id/touch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:visibility="invisible"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:background="#000">
<ImageView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:id="@+id/touchStatusImg"/>
<TextView
android:id="@+id/touch_time"
android:layout_width="wrap_content"
android:text="25:00/59:00"
android:textSize="12sp"
android:textColor="#fff"
android:layout_height="wrap_content"/>
</LinearLayout>
</FrameLayout>
上面的布局很简单,VideoView用了自定义是因为当布局改变的时候,要让VideoView重新获取布局位置,在里面设置它的分辨率为全屏.VideoView的代码如下
public class MyVideoView extends VideoView {
public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyVideoView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
this.getHolder().setFixedSize(width,height);//设置分辨率
setMeasuredDimension(width, height);
}
}
好,布局写好了我来第二步,获取内部控件初始化事件
2、第二步,onFinishInflate()中得到内部的控件,做初始化工作
onFinishInflate方法在xml解析完毕的时候会回调该方法,一般在做组合控件的时候最常用
@Override
protected void onFinishInflate() {
super.onFinishInflate();
initView();
}
private void initView() {
View view = LayoutInflater.from(context).inflate(R.layout.common_video_view,null);
viewBox = (FrameLayout) view.findViewById(R.id.viewBox);
videoView = (MyVideoView) view.findViewById(R.id.videoView);
videoPauseBtn = (LinearLayout) view.findViewById(R.id.videoPauseBtn);
screenSwitchBtn = (LinearLayout) view.findViewById(R.id.screen_status_btn);
videoControllerLayout = (LinearLayout) view.findViewById(R.id.videoControllerLayout);
touchStatusView = (LinearLayout) view.findViewById(R.id.touch_view);
touchStatusImg = (ImageView) view.findViewById(R.id.touchStatusImg);
touchStatusTime = (TextView) view.findViewById(R.id.touch_time);
videoCurTimeText = (TextView) view.findViewById(R.id.videoCurTime);
videoTotalTimeText = (TextView) view.findViewById(R.id.videoTotalTime);
videoSeekBar = (SeekBar) view.findViewById(R.id.videoSeekBar);
videoPlayImg = (ImageView) view.findViewById(R.id.videoPlayImg);
videoPlayImg.setVisibility(GONE);
videoPauseImg = (ImageView) view.findViewById(R.id.videoPauseImg);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
videoPauseBtn.setOnClickListener(this);
videoSeekBar.setOnSeekBarChangeListener(this);
videoPauseBtn.setOnClickListener(this);
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
screenSwitchBtn.setOnClickListener(this);
videoPlayImg.setOnClickListener(this);
//注册在设置或播放过程中发生错误时调用的回调函数。如果未指定回调函数,或回调函数返回false,VideoView 会通知用户发生了错误。
videoView.setOnErrorListener(this);
viewBox.setOnTouchListener(this);
viewBox.setOnClickListener(this);
addView(view);
}
很简单的做了代码初始化和videoView的播放事件的注册,这里的事件待会要处理的有viewBox.setOnTouchListener(this)注册的onTouch事件,我们要在里面写左右滑动快进快退的效果,videoSeekBar.setOnSeekBarChangeListener(this);处理拖动seekbar快进快退。
3、第三步,事件、效果处理
viewBox.setOnTouchListener(this);
1、onTouch里的实现快进快退
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//没播放的时候不处理
if (!videoView.isPlaying()){
return false;
}
float downX = event.getRawX();
touchLastX = downX;
Log.d("FilmDetailActivity", "downX" + downX);
//保存当前播放的位置用与做事件显示
this.position = videoView.getCurrentPosition();
break;
case MotionEvent.ACTION_MOVE:
//没播放的时候不处理
if (!videoView.isPlaying()){
return false;
}
float currentX = event.getRawX();
float deltaX = currentX - touchLastX;
float deltaXAbs = Math.abs(deltaX);
if (deltaXAbs>1){//正向移动,快进
if (touchStatusView.getVisibility()!=View.VISIBLE){
touchStatusView.setVisibility(View.VISIBLE);
//显示快进的时间view
}
touchLastX = currentX;
Log.d("FilmDetailActivity","deltaX"+deltaX);
if (deltaX > 1) {
position += touchStep;
if (position > duration) {
position = duration;
}
touchPosition = position;
touchStatusImg.setImageResource(R.mipmap.ic_fast_forward_white_24dp);
int[] time = getMinuteAndSecond(position);
touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
} else if (deltaX < -1) {//快退
position -= touchStep;
if (position < 0) {
position = 0;
}
touchPosition = position;
touchStatusImg.setImageResource(R.mipmap.ic_fast_rewind_white_24dp);
int[] time = getMinuteAndSecond(position);
touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
//mVideoView.seekTo(position);
}
}
break;
case MotionEvent.ACTION_UP:
if (touchPosition!=-1){
videoView.seekTo(touchPosition);
//放开手指的时候快进或快退到滑动决定的时间位置 touchStatusView.setVisibility(View.GONE);
touchPosition = -1;
if (videoControllerShow){
return true;
}
}
break;
}
return false;
}
2、处理 seekBar的拖动事件
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int[] time = getMinuteAndSecond(progress);
videoCurTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
//设置底部时间显示
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
videoView.pause();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
videoView.seekTo(videoSeekBar.getProgress());
videoView.start();
videoPlayImg.setVisibility(View.INVISIBLE);
videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
//拖动之后到指定的时间位置
}
3、videoView的回调事件
@Override
public void onPrepared(MediaPlayer mp) {
duration = videoView.getDuration();
int[] time = getMinuteAndSecond(duration);
videoTotalTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
formatTotalTime = String.format("%02d:%02d", time[0], time[1]);
videoSeekBar.setMax(duration);
progressBar.setVisibility(View.GONE);
mp.start();
videoPauseBtn.setEnabled(true);
videoSeekBar.setEnabled(true);
videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
timer.schedule(timerTask, 0, 1000);
//初始化总时间等一些界面显示,同时用timer定时去修改时间进度textView
}
@Override
public void onCompletion(MediaPlayer mp) {
videoView.seekTo(0);
videoSeekBar.setProgress(0);
videoPauseImg.setImageResource(R.mipmap.icon_video_play);
videoPlayImg.setVisibility(View.VISIBLE);
}
还有一些其它的点击时间就不放了,都是暂停,播放,点击显示隐藏底部状态栏,全屏切换等的事件。到了这里我们的事件处理完毕啦,接下来就要我们视频怎么播放呢?为了播放我们为外部提供一个方法
//开始播放
public void start(String url){
videoPauseBtn.setEnabled(false);
videoSeekBar.setEnabled(false);
videoView.setVideoURI(Uri.parse(url));
videoView.start();
}
//进入全屏时候调用
public void setFullScreen(){
touchStatusImg.setImageResource(R.mipmap.iconfont_exit);
this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
videoView.requestLayout();
}
//退出全屏时候调用
public void setNormalScreen(){
touchStatusImg.setImageResource(R.mipmap.iconfont_enter_32);
this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
videoView.requestLayout();
}
上面提供的setFullScreen()和setNormalScreen()需要在Activity的 onConfigurationChanged(Configuration newConfig)横竖屏发生改变的 回调方法里面调用,还需要注意的是我这里写的是LinearLayout的LayoutParams,所以我们自定义的view的父空间要是LinearLayout,当然你也可以修改。
4、控件的使用
我们只需要在获得空间调用start方法,然后在onConfigurationChanged方法里调用setFullScreen和setNormalScreen就可以了,
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.qiangyu.test.commonvideoview.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
<com.qiangyu.test.commonvideoview.CommonVideoView
android:id="@+id/common_videoView"
android:layout_width="match_parent"
android:layout_height="300dp" />
</LinearLayout>
activity代码
public class MainActivity extends AppCompatActivity {
CommonVideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
videoView = (CommonVideoView) findViewById(R.id.common_videoView);
videoView.start("你的服务器视频地址");
}
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
videoView.setFullScreen();
}else {
videoView.setNormalScreen();
}
}
}
最后为了防止你的Activity在横竖屏切换的时候重新创建别忘记在AndroidManifest.xml文件里面配置
android:configChanges=”orientation|screenSize|screenLayout”, 如果你这里有疑惑可以参考我的文章–>深入了解Activity-生命周期
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
好了,整个控件到这里就完成了,源码下载请戳它—>欢迎戳我
如果你觉得有对你有帮助,请动动手给个赞吧,有问题记得反馈给我哦!
使用VideoView自定义一个播放器控件的更多相关文章
- C# Winform开发程序调用VLC播放器控件播放视频.
VLC是个好东西,支持的格式多,还无广告,关键还有调用它的播放控件不用安装. 开个文章记录下调用这个控件的流水账,以便以后需要的时候查阅 创建工程 首先新建一个Winform工程. 这里姑且叫做VLC ...
- C#播放器控件的常用方法介绍
右击工具箱->选择项(I)... -> 显示"选择工具箱项" -> COM组件 -> Windows Media Player wmp.dll 添加 [ ...
- Delphi 媒体播放器控件
樊伟胜
- UWP 播放媒体控件
最近我的uwp需要有一个有声朗读的功能,like this 点击声音按钮就可以有声朗读了.这里主要是用了媒体播放的控件. 一般我们把需求分为两种: 一种是不需要呈现播放器的样子,只需要用户点击一下别的 ...
- WPF自定义LED风格数字显示控件
原文:WPF自定义LED风格数字显示控件 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/de ...
- 获取 AlertDialog自定义的布局 的控件
AlertDialog自定义的布局 效果图: 创建dialog方法的代码如下: 1 LayoutInflater inflater = getLayoutInflater(); 2 View layo ...
- IOS的segmentedControl(分段器控件)的一些常用属性
#pragma mark - 创建不同的分段器 //初始化方法:传入的数组可以是字符串也可以是UIImage对象的图片数组 UISegmentedControl *mysegmented = [[UI ...
- 如何用 Swift 语言构建一个自定控件
(via:破船之家,原文:How To Make a Custom Control in Swift) 用户界面控件是所有应用程序重要的组成部分之一.它们以图形组件的方式呈现给用户,用户可以通过它 ...
- android - 自定义(组合)控件 + 自定义控件外观
转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观 A ...
随机推荐
- C#基础课程之六(临时表)DataTable使用方法
DataTable 用法:赋取值操作,及报错情况 dataTable.Columns.Add("Name"); //Columns 对象获取该集合的全部列,添加列名. 默认stri ...
- 【原创】C#搭建足球赛事资料库与预测平台(4) 比赛信息数据表设计
本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新 开源C#彩票数据资料库系列文章总目录:[目录]C#搭建足球赛事资料库与预测平台与彩票数据分析目录 本篇文章开始将逐步介 ...
- 将excel导入mysql(使用navicat)
excel: 注: 1.mysql里建立一张跟excel一样的表结构的表(包含id) 2.excel最好没有任何格式,只是纯值,不然会出现导入不了的错误 ----------------------- ...
- 【第〇课】用更好的IDE开发
推荐的IDE IntelliJ IDEA JetBrains是一家捷克的软件开发公司,这家公司做出了很多的优秀的IDE,有写python的.php的.web前端的……IDEA就是其中一款非常优秀的ja ...
- cart中回归树的原理和实现
前面说了那么多,一直围绕着分类问题讨论,下面我们开始学习回归树吧, cart生成有两个关键点 如何评价最优二分结果 什么时候停止和如何确定叶子节点的值 cart分类树采用gini系数来对二分结果进行评 ...
- 写出优美代码的两个方式:一步到位VS迭代优化
最近把手头这个安卓APP的所有事务性方法都写完了,有了以下体会,新手体会,老鸟轻拍 想写成优美代码的人一般都会有这样的想法: 一定要在写每一句代码,写每一个方法,构造每一个类的时候,都要记得优化: ...
- 1215 spring 3 项目更新
列志华 (组长) http://www.cnblogs.com/liezhihua/ 团队guihub https://github.com/LWHTF/OrderingFood 黄柏堂 http:/ ...
- 改变Visual Studio 2012的皮肤
习惯了用vs的绿色背景,vs2012有自己的主题管理工具--Theme Editor vs2012默认没有安装Theme Editor,菜单:工具->扩展和更新,搜索栏里面输入Theme Edi ...
- Vs2012出现停止工作问题的解决方法
我的VS2012总是出现问题,打开项目会,更改移动控件位置也会,后来在网上找到了解决方法 这是出现问题
- 遇上了artTemplate做的东西
js现在最牛的地方是 有了Node.js后,前后端的界限几乎都消失了,围绕着它,出现了一整套生态体系. 在生态方面,比php好太多了.