注册广播有两种方式:动态注册和静态注册
静态注册:在功能清单文件注册,只要软件安装在手机上,就算软件不启动,也能收到对应的广播;
但是不是所有的广播都可以静态注册,比如电量的变化,亮屏息屏是不可以使用静态注册的。
动态注册:只有注册的代码被执行后,才能收到对应的广播
接下来动态注册一个对电池电量变化监听的广播。
private MyReceiver receiver;
private void initData() {
utils = new Utils();
//注册电量广播
receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
//当电量变化的时候发送这个广播,监听电池变化
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(receiver, filter);
}
class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra("level", 0);//获取当前电量
setBattery(level);
}
}
private void setBattery(int level) {
if (level <= 0){
ivBattery.setImageResource(R.drawable.ic_battery_0);
} else if (level <= 10){
ivBattery.setImageResource(R.drawable.ic_battery_10);
} else if (level <= 20){
ivBattery.setImageResource(R.drawable.ic_battery_20);
}else if (level <= 40){
ivBattery.setImageResource(R.drawable.ic_battery_40);
}else if (level <= 60){
ivBattery.setImageResource(R.drawable.ic_battery_60);
}else if (level <= 80){
ivBattery.setImageResource(R.drawable.ic_battery_80);
}else if (level <= 100){
ivBattery.setImageResource(R.drawable.ic_battery_100);
}else {
ivBattery.setImageResource(R.drawable.ic_battery_100);
}
}
注册广播后还需要在活动销毁的时候去取消这个广播:
@Override
protected void onDestroy() {
//释放资源的时候,需要先释放子类,再释放父类,也就是把super.onDestroy()放在最后
if (receiver != null){
unregisterReceiver(receiver);
receiver = null;
LogUtil.e("广播没啦!");
}
super.onDestroy();
}
从上面可以看到,电量已经可以显示了,不过时间还是没有设置的,现在就要去设置系统时间。
由于之前设置的Handler每一秒发送一次message,所以在之前的handler中添加:
//设置系统时间
tvSystemTime.setText(getSystemTime());
然后是具体方法:
private String getSystemTime() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
return format.format(new Date());
}
看一下效果
然后是传递列表数据到Activity中
列表中的数据有可能是对象或者String,int;
如果要传列表数据是对象,需要将对象序列化
在VideoPager的OnItemClickListener中,添加:
Intent intent = new Intent(context,SystemVideoPlayer.class);
Bundle bundle = new Bundle();
bundle.putSerializable("videolist", mediaItems);
intent.putExtras(bundle);
intent.putExtra("position", position);
context.startActivity(intent);
然后在SystemVideoActivity中添加:
private ArrayList<MediaItem> mediaItems;
private int position;
private void getData() {
//获取播放地址
uri = getIntent().getData();
mediaItems = (ArrayList<MediaItem>) getIntent().getSerializableExtra("videolist");
position = getIntent().getIntExtra("position", 0);
}
private void setData() {
if (mediaItems != null && mediaItems.size() > 0){
MediaItem mediaItem = mediaItems.get(position);
tvName.setText(mediaItem.getName()); //设置视频名称
videoview.setVideoPath(mediaItem.getData()); //设置播放地址
} else if (uri != null){
tvName.setText(uri.toString());
videoview.setVideoURI(uri);
} else{
Toast.makeText(this, "没有数据~~~~", Toast.LENGTH_SHORT).show();
}
}
然后给MediaItem这个类加上接口
public class MediaItem implements Serializable
就可以使用了
然后去给播放上一个视频和下一个视频的按钮添加具体的监听事件。需要做到,只有一个视频的时候,上一个和下一个按钮设置灰色,且不可点击,以此类推。
在播放下一个视频的点击监听中的添加playNextVideo()方法:
private void playNextVideo() {
if (mediaItems != null && mediaItems.size() > 0){
//播放下一个
position++;
if (position < mediaItems.size()){
MediaItem mediaItem = mediaItems.get(position);
tvName.setText(mediaItem.getName());
videoview.setVideoPath(mediaItem.getData());
//设置按钮状态
setButtonState();
}
} else if (uri != null){ //如果uri不为空,说明只有一个视频
//上一个和下一个按钮设置灰色,且不可点击
setButtonState();
}
}
然后是setButtonState()方法:
private void setButtonState() {
if (mediaItems != null && mediaItems.size() > 0){
if (mediaItems.size() == 1){
setEnable(false);
} else if (mediaItems.size() == 2){
if (position == 0){
btnVideoPre.setBackgroundResource(R.drawable.btn_pre_gray);
btnVideoPre.setEnabled(false);
btnVideoNext.setBackgroundResource(R.drawable.btn_video_next_selector);
btnVideoNext.setEnabled(true);
} else if (position == mediaItems.size() -1){
btnVideoNext.setBackgroundResource(R.drawable.btn_next_gray);
btnVideoNext.setEnabled(false);
btnVideoPre.setBackgroundResource(R.drawable.btn_video_pre_selector);
btnVideoPre.setEnabled(true);
}
}else{
if (position == 0){
btnVideoPre.setBackgroundResource(R.drawable.btn_pre_gray);
btnVideoPre.setEnabled(false);
} else if (position == mediaItems.size() -1){
btnVideoNext.setBackgroundResource(R.drawable.btn_next_gray);
btnVideoNext.setEnabled(false);
} else {
setEnable(true);
}
}
}else if (uri != null){
setEnable(false);
}
}
private void setEnable(boolean isEnable) {
if(isEnable){
//还原两个按钮状态
btnVideoPre.setBackgroundResource(R.drawable.btn_video_pre_selector);
btnVideoPre.setEnabled(true);
btnVideoNext.setBackgroundResource(R.drawable.btn_video_next_selector);
btnVideoNext.setEnabled(true);
}else{
//两个按钮设置灰色
btnVideoPre.setBackgroundResource(R.drawable.btn_pre_gray);
btnVideoPre.setEnabled(false);
btnVideoNext.setBackgroundResource(R.drawable.btn_next_gray);
btnVideoNext.setEnabled(false);
}
}
同时,视频播放完成时候调用的MyOnCompletionListener中也添加playNextVideo()方法,这样会自动播放下一个视频。
接下来为播放上一个视频按钮添加事件playPreVideo():
private void playPreVideo() {
if(mediaItems != null && mediaItems.size()>0){
//播放上一个视频
position--;
if(position >= 0){
MediaItem mediaItem = mediaItems.get(position);
tvName.setText(mediaItem.getName());
videoview.setVideoPath(mediaItem.getData());
//设置按钮状态
setButtonState();
}
}else if(uri != null){
//设置按钮状态-上一个和下一个按钮设置灰色并且不可以点击
setButtonState();
}
}
将切换视频按钮设置完成后,然后为播放器设置一下进入播放器时默认隐藏所有按钮界面,点击一下播放界面则显示,再点则隐藏,这就需要使用到手势识别器。
手势识别器
1.定义
private GestureDetector detector;//定义手势识别器
2.实例化
重写 双击——onDoubleTap,单击——onSingleTapConfirmed,长按——onLongPress
3.重写onTouchEvent()方法,并且把事件传递给手势识别器
//把事件传递给手势识别器
detector.onTouchEvent(event);
可以在initData中去实例化手势识别器:
//实例化手势识别器,并且重写双击,点击,长按
detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener(){
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
Toast.makeText(SystemVideoPlayer.this, "长按!!~~", Toast.LENGTH_SHORT).show();
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(SystemVideoPlayer.this, "双击!!!~~~", Toast.LENGTH_SHORT).show();
startAndPause();
return super.onDoubleTap(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Toast.makeText(SystemVideoPlayer.this, "单击!!!~~~", Toast.LENGTH_SHORT).show();
if (isShowMediaController){
hideMediaController();
//隐藏状态下去移除消息
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
} else {
showMediaController();
//只要处于显示状态就发消息去隐藏,就是默认过几秒后隐藏控制面板
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER, 3000);
}
return super.onSingleTapConfirmed(e);
}
});
private final static int HIDE_MEDIA_CONTROLLER = 2; //隐藏控制面板
private boolean isShowMediaController = false;
//显示控制面板
private void showMediaController(){
media_controller.setVisibility(View.VISIBLE);
isShowMediaController = true;
}
//隐藏控制面板
private void hideMediaController(){
media_controller.setVisibility(View.GONE);
isShowMediaController = false;
}
//控制播放暂停
private void startAndPause() {
if(videoview.isPlaying()){
//视频在播放-设置暂停
videoview.pause();
//按钮状态设置播放
btnVideoStartPause.setBackgroundResource(R.drawable.btn_video_start_selector);
}else{
//视频播放
videoview.start();
//按钮状态设置暂停
btnVideoStartPause.setBackgroundResource(R.drawable.btn_video_pause_selector);
}
}
在Handler中添加:
case HIDE_MEDIA_CONTROLLER:
hideMediaController();
break;
在MyOnPreparedListener中添加hideMediaController(),表示默认隐藏控制面板。
然后就可以单击显示隐藏控制面板了,不过这时候还有一个bug,就是滑动进度条的时候如果超过了3秒,控制面板还是会消失。
所以需要在SeekBar滑动的时候去移除handler的消息,于是在SeekBar的监听事件onStartTrackingTouch中去移除消息,也就是手指椅接触SeekBar的时候就移除:
//当手指触碰的时候回调这个方法
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
}
同时在手指离开SeekBar的时候让handler继续发送消息:
//当手指离开的时候回调这个方法
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER, 2000);
}
同时在控制面板上面各个按钮的点击事件后面重新让handler发送消息:
@Override
public void onClick(View v) {
...
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER, 2000);
}
单击和双击事件完成了,接下来为长按来添加事件,就是把长按的功能设置为放大缩小VideoView。需要去自定义VideoView才行。
创建MyVideoView继承VideoView,然后把之前布局中的VideoView和代码中的VideoView都换成MyVideoView:
public class MyVideoView extends VideoView {
//在代码中创建的时候一般用这个方法
public MyVideoView(Context context) {
this(context, null);
}
//当这个类在布局文件的时候,系统通过该构造方法实例化该类
public MyVideoView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
//当需要设置样式的时候调用该方法
public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
//设置视频的宽和高
public void setVideoSize(int videoWidth,int videoHeight){
ViewGroup.LayoutParams params = getLayoutParams();
params.width = videoWidth;
params.height = videoHeight;
setLayoutParams(params);
}
}
首先在onPrepared()中获取视频的真实宽和高:
private int videoWidth; //真实视频的宽
private int videoHeight;//真实视频的高
@Override
public void onPrepared(MediaPlayer mp) {
videoWidth = mp.getVideoWidth();
videoHeight = mp.getVideoHeight();
...
}
然后回到SystemVideoPlay的initData方法,获取屏幕的宽和高:
private int screenWidth = 0; //屏幕的宽
private int screenHeight = 0; //屏幕的高
private void initData() {
...
//得到屏幕的宽和高最新方式
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
screenWidth = displayMetrics.widthPixels;
screenHeight = displayMetrics.heightPixels;
}
private boolean isFullScreen = false; //判断是否强制拉伸全屏,false表示默认全屏
private static final int FULL_SCREEN = 1; //拉伸全屏状态
private static final int DEFAULT_SCREEN = 2; //默认全屏状态,就是不强制拉伸
private void setVideoType(int defaultScreen) {
switch (defaultScreen){
case FULL_SCREEN:
//1.设置视频画面的大小-屏幕有多大就是多大
videoview.setVideoSize(screenWidth, screenHeight);
//2.设置按钮的状态-默认
btnVideoSiwchScreen.setBackgroundResource(R.drawable.btn_video_switch_screen_default_selector);
isFullScreen = true;
break;
case DEFAULT_SCREEN:
//1.设置视频画面的大小
//视频真实的宽和高
int mVideoWidth = videoWidth;
int mVideoHeight = videoHeight;
//屏幕的宽和高
int width = screenWidth;
int height = screenHeight;
if ( mVideoWidth * height < width * mVideoHeight ) {
width = height * mVideoWidth / mVideoHeight;
} else if ( mVideoWidth * height > width * mVideoHeight ) {
height = width * mVideoHeight / mVideoWidth;
}
videoview.setVideoSize(width,height);
//2.设置按钮的状态--全屏
btnVideoSiwchScreen.setBackgroundResource(R.drawable.btn_video_switch_screen_full_selector);
isFullScreen = false;
break;
default:
break;
}
}
接着在长按的监听onLongPress中添加:
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
// Toast.makeText(SystemVideoPlayer.this, "长按!!~~", Toast.LENGTH_SHORT).show();
if (isFullScreen){
//默认不拉伸全屏状态
setVideoType(DEFAULT_SCREEN);
} else {
//强制拉伸全屏状态
setVideoType(FULL_SCREEN);
}
}
然后在onPrepared中去设置默认进入视频的时候视频是处于默认全屏状态的:
setVideoType(DEFAULT_SCREEN); //设置默认全屏
接下来需要为切换屏幕大小的按钮去添加这个点击事件,首先把之前的判断处于默认还是强制全屏状态的方法添加到一个方法中:
private void setFullScreenAndDefault() {
if (isFullScreen){
//默认不拉伸全屏状态
setVideoType(DEFAULT_SCREEN);
} else {
//强制拉伸全屏状态
setVideoType(FULL_SCREEN);
}
}
然后在按钮的点击事件中添加:
else if ( v == btnVideoSiwchScreen ) {
setFullScreenAndDefault();
}
这样就完成了视频画面大小的切换。
然后是调节声音功能
1.实例化AudioManger
当前的音量
最大音量
2.SeekBar.setMax(最大音量)
SeekBar.setProgress(当前的音量);
3.设置SeekBar状态变化
首先在initData去得到音量:
private AudioManager am; //调用声音
private int currentVoice; //当前音量
private int maxVoice; //最大音量
private void initData() {
//得到音量
am = (AudioManager) getSystemService(AUDIO_SERVICE);
currentVoice = am.getStreamVolume(AudioManager.STREAM_MUSIC);
maxVoice = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
}
然后在findViews()中去设置音量:
//设置最大音量以及当前音量
seekbarVoice.setMax(maxVoice);
seekbarVoice.setProgress(currentVoice);
接着在setListener()中添加一个对音量SeekBar的监听,用于设置状态的变化:
private void setListener(){
...
seekbarVoice.setOnSeekBarChangeListener(new VoiceOnSeekBarChangeListener())
}
class VoiceOnSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener{
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser){
if (progress > 0){
isMute = false;
} else {
isMute = true;
}
updataVoice(progress, isMute);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
//更新音量的变化,判断静音状态和非静音状态
private void updataVoice(int progress, boolean isMute) {
if (isMute){
am.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); //1表示调出系统的音量栏,0表示不调出
seekbarVoice.setProgress(0);
} else {
am.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0); //1表示调出系统的音量栏,0表示不调出
seekbarVoice.setProgress(progress);
currentVoice = progress;
}
}
然后给音量按钮添加点击事件:
private boolean isMute = false;//是否为静音
@Override
public void onClick(View v) {
if ( v == btnVoice ) {
isMute = !isMute; //取反
updataVoice(currentVoice, isMute);
}
...
}
接下来音量栏就可以正常操控音量了。
在屏幕滑动改变声音
不过这样还不够cool,下面可以设置依靠上下滑动屏幕去控制音量的增减,所以这就需要知道屏幕的坐标是怎么回事了。一开始我一直以为屏幕在第四象限,结果发现不是这样的,屏幕所在的坐标系相当于是把第一象限在X轴方向上翻转了180°,见图:
所以它的坐标计算是像上面这样的,Y轴正方向反过来了。
往下滑动
float distanceY = startY - endY < 0;
往上滑动
float distanceY = startY - endY > 0;
滑动屏幕的距离: 总距离 = 改变声音:音量最大值
改变声音 = (滑动屏幕的距离: 总距离)*音量最大值
最终声音 = 原来的 + 改变声音;
所以这就需要借助onTouchEvent方法。
private float startY; //Y轴起始坐标
private float touchRang; //屏幕的高
private int mVol; //手指按下那一刻的音量
@Override
public boolean onTouchEvent(MotionEvent event) {
//把事件传递给手势识别器
detector.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN://手指按下
//1.按下记录值
startY = event.getY();
mVol = am.getStreamVolume(AudioManager.STREAM_MUSIC);
touchRang = Math.min(screenHeight, screenWidth);//由于是横屏播放,所以高取小值
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
break;
case MotionEvent.ACTION_MOVE://手指移动
//2.移动的记录相关值
float endY = event.getY();
float distanceY = startY - endY;
//改变声音 = (滑动屏幕的距离: 总距离)*音量最大值
float delta = (distanceY/touchRang)*maxVoice;
//最终声音 = 原来的 + 改变声音;
int voice = (int) Math.min(Math.max(mVol+delta,0),maxVoice);//不错的判断方法,所以说学好算法很重要
if(delta != 0){
isMute = false;
updataVoice(voice,isMute);
}
// startY = event.getY();//不要加
break;
case MotionEvent.ACTION_UP://手指离开
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER,3000);
break;
}
return super.onTouchEvent(event);
}
这样,手指上下滑动改变音量大小就实现了,不过这时候还有个问题。
就是按下手机的物理音量键的时候,播放器内置的音量条是不会改变的,所以这时候就需要去重写onKeyDown的方法:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN){ //按下音量减的时候
currentVoice --;
updataVoice(currentVoice, false);
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER, 3000);
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){ //按下音量加的时候
currentVoice ++;
updataVoice(currentVoice, false);
handler.removeMessages(HIDE_MEDIA_CONTROLLER);
handler.sendEmptyMessageDelayed(HIDE_MEDIA_CONTROLLER, 3000);
}
return super.onKeyDown(keyCode, event);
}
这样就OK了!