原生的视频播放器无法满足现阶段丰富的需求,所以这里简单介绍一下几个常用功能的自定义写法。
概括
--UI
--播放与暂停
--视频进度条
--音量进度条
--全屏
--手势操作
这里不po界面的代码了,结构很简单,一个视屏播放控件和他的进度条,下面有个控制器,左下角是播放控制按钮,依次向右是已播放时间和播放总时间,然后有一个全屏时才显示的音量图标以及音量进度条,最后是全屏切换按钮。
由于是第一次做进度条的界面,所以这里留下进度条的笔记(以视频进度条)。
<!-- indeterminate 是否允许使用不确定模式,在不确定模式下,无法设置进度条的值-->
<SeekBar
android:id="@+id/play_seek"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_marginLeft="-20dp"
android:layout_marginRight="-20dp"
android:indeterminate="false"
android:max="100"
android:progress="20"
android:progressDrawable="@drawable/seekbar_style2"
android:thumb="@null" />
进度条的背景:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#707070" />
<size android:height="5dp" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#994310" />
<size android:height="5dp" />
</shape>
</clip>
</item>
</layer-list>
这里使用了层叠的图形,背景是灰色,上面进度条的部分是橘色,这样设置进度时,就可以达到进度条滚动的效果。
播放与暂停
播放和暂停的响应比较简单,但是这里和下面会介绍的进度条的控制一样牵涉到播放时间的显示,所以首先我们需要为时间绑定一个每隔一段时间响应的handler,让他及时的 刷新时间显示。
private Handler UIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == UPDATE_UI) {
// 获取视频当前播放时间
int currentPosition = videoView.getCurrentPosition();
// 获取视频播放总时间
int totalduration = videoView.getDuration();
// 格式化视频播放时间
updateTextViewWithTimeFormat(time_current_tv, currentPosition);
updateTextViewWithTimeFormat(time_total_tv, totalduration);
play_seek.setMax(totalduration);
play_seek.setProgress(currentPosition);
UIHandler.sendEmptyMessageDelayed(UPDATE_UI, 500);
}
}
};
当我们接受到更新时间的信息是,就主动更新时间,同时暂停和播放的时候要暂停和启动这个时间响应。设置了0.5秒的延迟,使得启动之后能不断的刷新时间。
下面是播放和暂停的事件:
play_controller_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (videoView.isPlaying()) {
play_controller_img.setImageResource(R.drawable.play_btn_selector);
videoView.pause();
UIHandler.removeMessages(UPDATE_UI);
} else {
play_controller_img.setImageResource(R.drawable.pause_btn_selector);
videoView.start();
UIHandler.sendEmptyMessage(UPDATE_UI);
}
}
});
主要做了三件事,切换播放按钮的图片,控制视频播放,控制时间刷新。
视频进度条
视频进度条需要响应手指的拖动事件,控制视频播放到拖动的位置,也要修改时间显示。
play_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
updateTextViewWithTimeFormat(time_current_tv, progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
UIHandler.removeMessages(UPDATE_UI);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
// 视频播放进度拖至滚动条位置
videoView.seekTo(progress);
UIHandler.sendEmptyMessage(UPDATE_UI);
}
});
这里progress传入的参数是播放的毫秒,可以根据他和总时间的百分比来更新时间。
音量进度条
想要控制音量,首先需要获取音量服务。
// 获取音频服务
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
在初始化进度条的时候也要用到最大音量和当前音量。
/**
* 获取设备最大音量
*/
int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
/**
* 获取当前设备音量
*/
int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
volume_seek.setMax(streamMaxVolume);
volume_seek.setProgress(streamVolume);
最后绑定进度条的拖动事件:
volume_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/**
* 设置当前音量
*/
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
全屏有两个模块,一个是全屏按钮的切换,一个是转动屏幕的切换,而且这两者同时设置的时候还会出现一些冲突。
首先,因为不希望屏幕方向等因素会影响视频的播放,也就是会重启activity导致视频重新播放的问题,需要在AndroidManifext.xml中做出如下设置:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
再来看全屏按钮的相应,如果横屏就转竖屏,如果竖屏就转横屏:
screen_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isFullScreen) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
});
然后是转动屏幕的切换:
这里有一个onConfigurationChanged函数可以用来监听当前屏幕的转动:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
/**
* 屏幕为横屏
*/
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
volume_img.setVisibility(View.VISIBLE);
volume_seek.setVisibility(View.VISIBLE);
isFullScreen = true;
}
/**
* 屏幕为竖屏
*/
else {
setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(this, 240));
volume_img.setVisibility(View.GONE);
volume_seek.setVisibility(View.GONE);
isFullScreen = false;
}
}
这里做了三件事,首先根据屏幕的方向来重新设置播放器的布局,然后控制音量图标和进度条的显示,最后重新设置isFullScreen变量。
手机竖屏的时候我们希望播放器高度只要240dp。
(这图片好大~~~~)
但是全屏的时候希望占满整个屏幕。
所以为了完成布局的重新布置,需要一个函数,更改视频播放器布局以及他外侧的布局:
private void setVideoViewScale(int width, int height) {
ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
layoutParams.width = width;
layoutParams.height = height;
videoView.setLayoutParams(layoutParams);
layoutParams = videoLayout.getLayoutParams();
layoutParams.width = width;
layoutParams.height = height;
videoLayout.setLayoutParams(layoutParams);
}
原因: setRequestedOrientation设置屏幕方向之后,比如说setRequestedOrientation(portrait)方法,就设定了屏幕方向是portrait,和在清单文件中配置Android:screenOrientation="portrait"是同等的效果;也即不再响应屏幕方向改变,只支持portrait方向;
解决方法:参考http://blog.csdn.net/u011449197/article/details/38843551
这里有一个改进,就是监听屏幕转动时角度的判断,解决方法中只监听了两种转向,如果多添加一些判断,就可以监听四种转向。
竖屏:
(((rotation >= 0) && (rotation <= 30)) || (rotation > 150 && rotation < 210) || (rotation >= 330))
横屏:
(((rotation >= 230) && (rotation <= 310)) || ((rotation >= 50) && (rotation <= 130)))
手势操作
在常用的视频软件中,可以通过手势操作,例如上滑和下滑来调节音量或者亮度,左右滑动控制播放进度,这里介绍上下滑动右侧界面调节音量、左侧界面调节亮度的功能实现。
videoView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float detlaX = x - lastX;
float detlaY = y - lastY;
float absdetlaX = Math.abs(detlaX);
float absdetlaY = Math.abs(detlaY);
if (absdetlaX > THRESHOLD && absdetlaY > THRESHOLD) {
if (absdetlaX < absdetlaY) {
isAdjust = true;
} else {
isAdjust = false;
}
} else if (absdetlaX < THRESHOLD && absdetlaY > THRESHOLD) {
isAdjust = true;
} else if (absdetlaX > THRESHOLD && absdetlaY < THRESHOLD) {
isAdjust = false;
}
if (isAdjust) {
/**
* 当前手势合法,区分是视频左部分(亮度)还是右部分(音量)
*/
if (x < screen_width / 2) {
// 调节亮度
if (detlaY > 0) {
// 降低亮度
} else {
// 提高亮度
}
changeBrightness(-detlaY);
} else {
//调节音量
if (detlaY > 0) {
// 降低音量
} else {
// 提高音量
}
changeVolume(-detlaY);
}
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
opetation_layout.setVisibility(View.GONE);
break;
default:
break;
}
return true;
}
});
为视频播放器添加触屏监听, 当触摸在左侧时,调节亮度,触摸在右侧时,调节音量。(常量THRESHOLD是20)
调节音量:
private void changeVolume(float detlaY) {
int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int index = (int) (detlaY / screen_height * max * 3);
int volume = Math.max(index + current, 0);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
if (opetation_layout.getVisibility() == View.GONE) {
opetation_layout.setVisibility(View.VISIBLE);
}
ViewGroup.LayoutParams params = operation_percent.getLayoutParams();
params.width = (int) (DisplayUtil.dip2px(this, 94) * (float) volume / max);
operation_percent.setLayoutParams(params);
volume_seek.setProgress(volume);
}
operation_percent是一个调节音量时显示的背景。
private void changeBrightness(float detleY) {
WindowManager.LayoutParams attributes = getWindow().getAttributes();
mBrightness = attributes.screenBrightness;
mBrightness += detleY / screen_height / 3;
if (mBrightness > 1.0f) {
mBrightness = 1.0f;
}
if (mBrightness < 0.01f) {
mBrightness = 0.01f;
}
attributes.screenBrightness = mBrightness;
getWindow().setAttributes(attributes);
}