Android自定义播放器控件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>

以上所述是小编给大家分享的Android自定义播放器控件VideoView的相关知识,希望对大家有所帮助。

(0)

相关推荐

  • 轻松实现可扩展自定义的Android滚轮时间选择控件

    项目需求中有个功能模块需要用到时间选择控件,但是android系统自带的太丑了,只能自己优化下,结合WheelView实现滚轮选择日期,好像网上也挺多这种文章的.但是适用范围还是不同,希望这个能够对需求相同的朋友有一定帮助.控件标题还有年月日时分秒这些可以自己控制是否显示,先来看效果. 1.有年月日时分的开始时间 2.只有年月日的结束时间 3.用于有时身份证到期的时间选择(分为勾选长期和直接选择时间两种,另外长期后面自己也可以进行扩展) 4.项目结构 5.直接贴代码,代码里面注释很详细 <spa

  • Android控件之RatingBar自定义星级评分样式

    一.RatingBar简单介绍 RatingBar是基于SeekBar(拖动条)和ProgressBar(状态条)的扩展,用星形来显示等级评定,在使用默认RatingBar时,用户可以通过触摸/拖动/按键(比如遥控器)来设置评分, RatingBar自带有两种模式 ,一个小风格 ratingBarStyleSmall,大风格为ratingBarStyleIndicator,大的只适合做指示,不适用与用户交互. 效果图展示: 二.实例 1.布局文件 <?xml version="1.0&qu

  • Android控件之ProgressBar用法实例分析

    本文实例讲述了Android控件之ProgressBar用法.分享给大家供大家参考.具体如下: ProgressBar位于android.widget包下,其继承于View,主要用于显示一些操作的进度.应用程序可以修改其长度表示当前后台操作的完成情况.因为进度条会移动,所以长时间加载某些资源或者执行某些耗时的操作时,不会使用户界面失去响应.ProgressBar类的使用非常简单,只需将其显示到前台,然后启动一个后台线程定时更改表示进度的数值即可. 以下ProgressBar跟Handle结合,模

  • Android自定义控件实现可左右滑动的导航条

    先上效果图: 这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来.因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来.  首先来分析下这个控件的功能:  •能够响应左右滑动,并且能响应快速滑动 •选择项和未选择项有不同的样式表现,比如前景色,背景色,字体大小变粗之内的 •在切换选项的时候,如果当前选项未完全呈现在界面前,则自动滚动直至当前选项完全暴露显示 前两条还有,简简单单就实现了,主要是第三点,这才是我自定义这个控件的原因!那么如果要实现这个控件,需要用到哪些知识呢? 

  • Android开发之自定义控件用法详解

    本文实例讲述了Android开发之自定义控件用法.分享给大家供大家参考,具体如下: 今天和大家分享下组合控件的使用.很多时候android自定义控件并不能满足需求,如何做呢?很多方法,可以自己绘制一个,可以通过继承基础控件来重写某些环节,当然也可以将控件组合成一个新控件,这也是最方便的一个方法.今天就来介绍下如何使用组合控件,将通过两个实例来介绍. 第一个实现一个带图片和文字的按钮,如图所示: 整个过程可以分四步走.第一步,定义一个layout,实现按钮内部的布局.代码如下: custom_bu

  • Android开发中自定义ProgressBar控件的方法示例

    本文实例讲述了Android开发中自定义ProgressBar控件的方法.分享给大家供大家参考,具体如下: 很简单,首先加载Drawable,在onMeasure设置好其区域大小, 然后使用canvas.clipRect绘图 public class ProgressView extends ImageView { private Drawable maskDraw; /** * 加载的进度 0-100 */ private int mProcess = 20; public ProgressV

  • Android自定义控件之圆形/圆角的实现代码

    一.问题在哪里? 问题来源于app开发中一个很常见的场景--用户头像要展示成圆的:  二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在头像上不就完事了嘛,哈哈哈! 在背景纯色的前提下,这的确能简单解决问题,但是如果背景没有这么简单呢? 在这种不规则背景下,有两个问题: 1).背景图常常是适应手机宽度缩放,而头像的尺寸又是固定宽高DP的,所以固定的蒙板图片是没法保证在不同机型上都和背景图案吻合的. 2).在这种非纯色背景下,哪天想调整一下头像

  • android开发教程之自定义控件checkbox的样式示例

    主界面xml文件 复制代码 代码如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_p

  • Android进度条控件progressbar使用方法详解

    一.简介 二.方法 1)进度条ProgressBar使用方法 1.在layout布局文件中创建ProgressBar控件 <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:progress="30&

  • android自定义倒计时控件示例

    自定义TextView控件TimeTextView代码: 复制代码 代码如下: import android.content.Context;import android.content.res.TypedArray;import android.graphics.Paint;import android.text.Html;import android.util.AttributeSet;import android.widget.TextView; import com.new0315.R;

  • android ListView和ProgressBar(进度条控件)的使用方法

    ListView控件的使用:ListView控件里面装的是一行一行的数据,一行中可能有多列,选中一行,则该行的几列都被选中,同时可以触发一个事件,这种控件在平时还是用得很多的.使用ListView时主要是要设置一个适配器,适配器主要是用来放置一些数据.使用起来稍微有些复杂,这里用的是android自带的SimpleAdapter,形式如下:android.widget.SimpleAdapter.SimpleAdapter(Context context, List<? extends Map<

随机推荐