工作需要实现一个在播放音乐时,音量键按下后弹出程序自己的音量控制界面,由于对android还不太熟悉,就当写个小Demo练练手吧。
根据要求的话,需要实现如下功能点的要求
1、 拦截系统音量键
2、 用SeekBar简单实现音量控制界面(采用对话框的形式来实现)
3、 点击对话框外内容,对话框自动消失
4、 对话框内一定时间内无事件(3s),对话框自动消失
下面分步来实现这个小Demo程序。
本来想在View中拦截音量键的,但是不知道为什么在View中onKeyDown事件拦截不到,后来查了下网上的说法,如果需要让一个View响应按键事件,必须按键的时候焦点位于这个View上面,我尝试了下,假设这个View类实例为test,则可以通过下列方式设置焦点
//test.setFocusable(true);
test.setFocusableInTouchMode(true);
test.requestFocus();
网上也有人说通过SetFocusable设置的就可以了,但是我试了下不行,必须得用setFocusableInTouchMode然后调用requestFocus,我在测试时放在OnCreate方法中调用,也有人说放在OnCreate中有时候也不行,因为此时View还没有被显示出来。后来问了下同事,说是这种在View中拦截OnKeyDown的方式是并不推荐的方法,有违面向对象的原则,OnKeyDown这种事件本身就应该交由Activity等类来拦截,然后需要处理的操作,就通过给View类配备set和get方法的形式来设置,想想也是很有道理的。
系统音量键的拦截网上代码很多,在这里也不多说,直接上代码吧,其中的VolumeSetDialog就是自己的音量控制对话框类。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
VolumeSetDialog vDlg = new VolumeSetDialog(this);
vDlg.show();
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
VolumeSetDialog vDlg = new VolumeSetDialog(this);
vDlg.show();
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
这里真没有什么补充的,因为我的对话框弹出时,跟系统的音量控制对话框是互斥的,所以onKeyDown返回为true,否则还是会显示出系统音量对话框出来哦。
系统提供的SeekBar是横向的,我本来以为可以通过类似android:orientation="vertical"的修改参数方法让其实现竖直方向,结果不行,貌似需要用自定义的seekBar,想着能力有限,就给几个链接吧,以后有需要再去看
android 竖直seekbar的案例,需要重载类
http://www.eoeandroid.com/thread-93431-1-1.html
http://blog.csdn.net/riyuegonghe/article/details/9298567
http://www.mokasocial.com/2011/02/create-a-custom-styled-ui-slider-seekbar-in-android/
我就使用默认的最简单的拖动方法产生了一个布局名为volume_dialog.xml,具体的内容如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/volume_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<SeekBar
android:id="@+id/volume_seekBar"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_centerInParent="true" />
</RelativeLayout>
注:上面的代码中
然后构建一个新的派生自AlertDialog的VolumeSetDialog类。在该类中,重载AlertDialog的onCreate函数,具体代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
LayoutInflater inflater = LayoutInflater.from(getContext());
View mRootView = getLayoutInflater().inflate(R.layout.dj_volume_dialog, null);
setContentView(mRootView);
// LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
// LayoutParams.MATCH_PARENT);
// addContentView(mRootView, params);
mVolumeSeekBar = (SeekBar) mRootView.findViewById(R.id.volume_seekBar);
mVolumeSeekBar.setOnSeekBarChangeListener(this);
//mVolumeSeekBar.setOnClickListener(this);
//mRootView.findViewById(R.id.volume_layout).setOnClickListener(this);
mAudioManager = (AudioManager) this.getContext().getSystemService(Context.AUDIO_SERVICE);
mMaxMusicVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mCurMusicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mVolumeSeekBar.setMax(mMaxMusicVolume);
mVolumeSeekBar.setProgress(mCurMusicVolume);
setCancelable(true);
setCanceledOnTouchOutside(true);
}
上述代码加红色字体是我这个初学者一开始犯错误的地方。刚开始没有调用addContentView和setContentView,发现调用dlg.show的时候,界面变灰了,但是始终没有出现SeekBar,后来问了同事才知道,需要吧inflater的View加载到对话框中才能显示出来。
后面两块红色字体其实属于第三项功能了。
刚开始考虑问题时,想到的是在SeekBar和整个根视图上添加OnClickListener,然后重载onClick(View v)方法,判断当前点击的是否为SeekBar,假如不是SeekBar就调用dismiss函数让整个对话框消失。
后来发现,其实onClick事件压根就没有被调到,后来给布局加上了一个button,结果点击的时候onClick事件才能响应,以为要设置SeekBar的clickable属性,结果设置了也是没有用。决定放弃,硬着头皮又去问同事,才知道对话框提供了setCanceledOnTouchOutside和setCancelable方法,前者设为true时,就能实现对话框外点击,对话框消失,设为false时,点击无响应;而后者则是对系统back键的响应与否,true则点击back键对话框消失。
在这里需要补充的时,原本以为布局文件要进行更改,不能使用match_parent,以为match_parent就覆盖了整个Windows,结果并非如我想的那样,应该是对话框属性默认就是将控件居中显示的,所以布局文件中就算将android:layout_centerInParent="true"属性去除,在弹出对话框显示时还是会居中显示SeekBar的。
至于具体的对话框样式,这里就不展开追究了。
这个功能的实现,可以使用Timer和TimerTask来实现,不过起初想的稍微有点复杂,把TimerTask也实例化一个对象来使用以便管理取消的任务,谁知道TimerTask实例在Timer取消的时候,压根也被释放掉了,不能重用,Timer实例对象一旦cancel也是不能被再次schedule了,否则会报错,具体可以参见如下两个链接的说明
http://blog.csdn.net/snowdream86/article/details/7072530
http://www.eoeandroid.com/thread-68735-1-1.html
废话不多说,感觉android真的不用你像C++一样需要考虑得太多,他很多都已经给你弄好了,上传一个简单的控制代码吧,呵呵。
private void startTimer() {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
VolumeSetDialog.this.dismiss();
mTimer.cancel();
mTimer = null;
}
}, 3000);
}
private void stopTimer() {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
好了,一个简单的小例子就介绍到这里,做完了感觉很简单,但是说实话,对于我这种android新手还是花了不少时间才搞懂的。