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的相关知识,希望对大家有所帮助。