Android自定义播放器控件VideoView
介绍
最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了。在写VideoView播放视频时候定义控制的代码全写在Actvity里了,写完一看我靠代码好乱,于是就写了个自定义的播放器控件,支持指定大小,可以横竖屏切换,手动左右滑动快进快退。好了,下面开始。
效果图有点卡,我也不知道为啥。。。。。
VideoView介绍
这个是我们实现视频播放最主要的控件,详细的介绍大家百度就去看,这里介绍几个常用的方法。
用于播放视频文件。VideoView类可以从不同的来源(例如资源文件或内容提供器)读取图像,计算和维护视频的画面尺寸以使其适用于任何布局管理器,并提供一些诸如缩放、着色之类的显示选项。
VideoView常用的几个方法
publicintgetDuration()
获得所播放视频的总时间
publicintgetCurrentPosition()
获得当前的位置,我们可以用来设置播放时间的显示
publicintgetCurrentPosition()
获得当前的位置,我们可以用来设置播放时间的显示
publicintpause()
暂停播放
publicintseekTo()
设置播放位置,我们用来总快进的时候就能用到
publicintsetOnCompletionListener(MediaPlayer.OnCompletionListenerl)
注册在媒体文件播放完毕时调用的回调函数。
publicintsetOnErrorListener(MediaPlayer.OnErrorListenerl)
注册在设置或播放过程中发生错误时调用的回调函数。如果未指定回调函数,或回调函数返回false,会弹一个dialog提示用户不能播放
publicvoidsetOnPreparedListener(MediaPlayer.OnPreparedListenerl)
注册在媒体文件加载完毕,可以播放时调用的回调函数。
publicvoidsetVideoURI(Uriuri)
设置播放的视频源,也可以用setVideoPath指定本地文件
publicvoidstart()
开始播放
getHolder().setFixedSize(width,height);
设置VideoView的分辨率,如果我们的VideoView在开始播放的时候是竖屏的,当横屏的时候我们改变了VideoView的布局大小,就需要这个方法重新设置它的分辨率,否则你会发现改变了之后VideoView内部的视频部分还是原来的大小,这点要注意。
自定义播放器思路
说是自定义,其实无非就是把这些VideoView和用来显示的其它控件结合在一起,然后在内部处理它的事件交互,我们要做的就是以下几步:1、写好整个空间的布局。2、在自定义控件的内部获取到整个控件内部的各个小控件,并且为它们设置一些初始化事件。3、根据你自己的逻辑和想实现的效果在里面写自己的事件处理,需要在和外部进行交互就提供方法和接口咯。最后就是使用测试效果了。好了,我们就跟着这里说的4步去实现吧!
具体实现
1、第一步,写自己的布局文件
想要的效果就是在底部放一个状态栏显示时间等信息,播放进度,进入全屏,中间放一个快进快退的状态,布局代码如下:
<?xmlversion="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"/> //底部状态栏 <LinearLayoutandroid: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"> <LinearLayoutandroid:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:id="@+id/videoPauseBtn" android:paddingRight="10dip" android:paddingLeft="10dp"> <ImageViewandroid:layout_width="22dp" android:layout_height="22dp" android:id="@+id/videoPauseImg"/> </LinearLayout> <LinearLayoutandroid:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="horizontal" android:paddingRight="0dip"> <SeekBarandroid:layout_width="fill_parent" android:id="@+id/videoSeekBar" android:layout_weight="1" style="@android:style/Widget.Holo.SeekBar" android:layout_height="wrap_content"/> <TextViewandroid: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="/"/> <TextViewandroid: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中间的开始按钮和进度条以及快进快退的提示 <ProgressBarandroid:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:id="@+id/progressBar" style="@android:style/Widget.Holo.ProgressBar.Small"/> <ImageViewandroid: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"> <ImageViewandroid: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的代码如下
publicclassMyVideoViewextendsVideoView{
publicMyVideoView(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);
}
publicMyVideoView(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
publicMyVideoView(Contextcontext){
super(context);
}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
intwidth=getDefaultSize(0,widthMeasureSpec);
intheight=getDefaultSize(0,heightMeasureSpec);
this.getHolder().setFixedSize(width,height);//设置分辨率
setMeasuredDimension(width,height);
}
}
好,布局写好了我来第二步,获取内部控件初始化事件
2、第二步,onFinishInflate()中得到内部的控件,做初始化工作
onFinishInflate方法在xml解析完毕的时候会回调该方法,一般在做组合控件的时候最常用
@Override
protectedvoidonFinishInflate(){
super.onFinishInflate();
initView();
}
privatevoidinitView(){
Viewview=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
publicbooleanonTouch(Viewv,MotionEventevent){
switch(event.getAction()){
caseMotionEvent.ACTION_DOWN:
//没播放的时候不处理
if(!videoView.isPlaying()){
returnfalse;
}
floatdownX=event.getRawX();
touchLastX=downX;
Log.d("FilmDetailActivity","downX"+downX);
//保存当前播放的位置用与做事件显示
this.position=videoView.getCurrentPosition();
break;
caseMotionEvent.ACTION_MOVE:
//没播放的时候不处理
if(!videoView.isPlaying()){
returnfalse;
}
floatcurrentX=event.getRawX();
floatdeltaX=currentX-touchLastX;
floatdeltaXAbs=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));
}elseif(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;
caseMotionEvent.ACTION_UP:
if(touchPosition!=-1){
videoView.seekTo(touchPosition);
//放开手指的时候快进或快退到滑动决定的时间位置touchStatusView.setVisibility(View.GONE);
touchPosition=-1;
if(videoControllerShow){
returntrue;
}
}
break;
}
returnfalse;
}
2、处理seekBar的拖动事件
@Override
publicvoidonProgressChanged(SeekBarseekBar,intprogress,booleanfromUser){
int[]time=getMinuteAndSecond(progress);
videoCurTimeText.setText(String.format("%02d:%02d",time[0],time[1]));
//设置底部时间显示
}
@Override
publicvoidonStartTrackingTouch(SeekBarseekBar){
videoView.pause();
}
@Override
publicvoidonStopTrackingTouch(SeekBarseekBar){
videoView.seekTo(videoSeekBar.getProgress());
videoView.start();
videoPlayImg.setVisibility(View.INVISIBLE);
videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
//拖动之后到指定的时间位置
}
3、videoView的回调事件
@Override
publicvoidonPrepared(MediaPlayermp){
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
publicvoidonCompletion(MediaPlayermp){
videoView.seekTo(0);
videoSeekBar.setProgress(0);
videoPauseImg.setImageResource(R.mipmap.icon_video_play);
videoPlayImg.setVisibility(View.VISIBLE);
}
还有一些其它的点击时间就不放了,都是暂停,播放,点击显示隐藏底部状态栏,全屏切换等的事件。到了这里我们的事件处理完毕啦,接下来就要我们视频怎么播放呢?为了播放我们为外部提供一个方法
//开始播放
publicvoidstart(Stringurl){
videoPauseBtn.setEnabled(false);
videoSeekBar.setEnabled(false);
videoView.setVideoURI(Uri.parse(url));
videoView.start();
}
//进入全屏时候调用
publicvoidsetFullScreen(){
touchStatusImg.setImageResource(R.mipmap.iconfont_exit);
this.setLayoutParams(newLinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
videoView.requestLayout();
}
//退出全屏时候调用
publicvoidsetNormalScreen(){
touchStatusImg.setImageResource(R.mipmap.iconfont_enter_32);
this.setLayoutParams(newLinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
videoView.requestLayout();
}
上面提供的setFullScreen()和setNormalScreen()需要在Activity的onConfigurationChanged(ConfigurationnewConfig)横竖屏发生改变的回调方法里面调用,还需要注意的是我这里写的是LinearLayout的LayoutParams,所以我们自定义的view的父空间要是LinearLayout,当然你也可以修改。
4、控件的使用
我们只需要在获得空间调用start方法,然后在onConfigurationChanged方法里调用setFullScreen和setNormalScreen就可以了,
布局
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns: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代码
publicclassMainActivityextendsAppCompatActivity{
CommonVideoViewvideoView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
videoView=(CommonVideoView)findViewById(R.id.common_videoView);
videoView.start("你的服务器视频地址");
}
@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){
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> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
以上所述是小编给大家分享的Android自定义播放器控件VideoView的相关知识,希望对大家有所帮助。