Android系统⾳量条实例代码
最近在定制Android系统⾳量条,发现代码还是蛮多的,下⾯总结⼀下。
代码是基于5.1.1版本的。
系统⾳量条的代码是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
布局⽂件是在/frameworks/base/packages/SystemUI/res/layout下。
先看看原⽣的⾳量条样式:
在代码中可以发现l这个⽂件,这个⽂件就是承载⾳量条的布局了,在layout⽂件夹到打开会发现这个布局很简单,只是include 了⼀个volume_panel。
volume_panel布局包含了⼀个id叫slider_panel的FrameLayout和include了⼀个zen_mode_panel,显然slider_panel后⾯会包含seekbar,看VolumePanel.java也会发现在代码中加载了volume_l这个⽂件,⼀看,发现⾥⾯就包含了seekbar这个控件啦。另外
zen_mode_panel是指勿扰模式。
在看这个布局⽂件的时候,你会看到android:clipChildren这个属性,它的作⽤:是否限制⼦View在其范围内,我们将其值设置为false后那么当⼦控件的⾼度⾼于⽗控件时也会完全显⽰,⽽不会被压缩。默认为true。
若想某个控件不显⽰,设置属性android:visibility=”gone”就好了。
看完布局,下⾯就主要看VolumePanel.java这个⽂件了。
VolumePanel下定义了两个重要的⼦类型,分别是StreamResources和StreamControl。StreamResources实际上是⼀个枚举,它的每⼀个可⽤元素保存了⼀个流类型的通知框所需要的各种资源,如图标、提⽰⽂字等。StreamResources的定义就像下⾯这样:
private enum StreamResources {
BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
R.string.volume_icon_description_bluetooth,
IC_AUDIO_BT,
IC_AUDIO_BT_MUTE,
false),
// 这⾥省略了后⾯的⼏个枚举项的构造参数,这些与BluetoothSCOStream的内容是⼀致的
RingerStream(...),
VoiceStream(...),
AlarmStream(...),
MediaStream(...),
NotificationStream(...),
// for now, use media resources for master volume
MasterStream(...),
RemoteStream(...);// will be dynamically updated
int streamType; // 流类型
int descRes; // 描述信息
int iconRes; // 图标
int iconMuteRes; // 静⾳图标
// RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
boolean show; // 是否显⽰
//构造函数
StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
...android layout布局
}
}
这⼏个枚举项组成了⼀个名为STREAM的数组,如下:
private static final StreamResources[] STREAMS = {
StreamResources.BluetoothSCOStream,
StreamResources.RingerStream,
StreamResources.VoiceStream,
StreamResources.MediaStream,
StreamResources.NotificationStream,
StreamResources.AlarmStream,
StreamResources.MasterStream,
StreamResources.RemoteStream
};
VolumePanel将从这个STREAMS数组中获取它所⽀持的流类型的相关资源。
StreamControl类则保存了⼀个流类型的通知框所需要显⽰的控件,其定义如下:
/** Object that contains data for each slider */
private class StreamControl {
int streamType;
MediaController controller;
ViewGroup group;
ImageView icon;
SeekBar seekbarView;
TextView suppressorView;
View divider;
ImageView secondaryIcon;
int iconRes;
int iconMuteRes;
int iconSuppressedRes;
}
StreamControl实例中保存了⾳量调节通知框中所需的所有控件。出于对运⾏效率的考虑,StreamControl实例也是每个流类型⼈⼿⼀份,和StreamResources实例形成⼀⼀对应的关系。所有的StreamControl实例被保存在⼀个以流类型的值为键的SparseArray中,名为mStreamControls。
可以在StreamControl的初始化函数createSliders()中看到。
private void createSliders() {
...
// 遍历STREAM中所有的StreamResources实例
for (int i = 0; i < STREAMS.length; i++) {
StreamResources streamRes = STREAMS[i];
final int streamType = streamRes.streamType;
...
final StreamControl sc = new StreamControl();// 为streamType创建⼀个StreamControl
// 下⾯将初始化sc的成员变量
...
sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); // 设置监听
mStreamControls.put(streamType, sc);// 将初始化好的sc放⼊mStreamControls中
}
}
private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
final Object tag = Tag();
if (fromUser && tag instanceof StreamControl) {
StreamControl sc = (StreamControl) tag;
//设置⾳量
setStreamVolume(sc, progress,AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
}
resetTimeout();
}
...
};
这个初始化的⼯作并没有在构造函数中进⾏,⽽是在postVolumeChanged()、postRemoteVolumeChanged()、postMuteChanged()函数中处理的。VolumePanel保存了⼀个名为mDialog的Dialog实例,这就是通知框的本⾝了。每当有新的⾳量变化到来时,mDialog的内容就会被替换为指定流类
型对应的StreamControl中所保存的控件,并且根据⾳量变化情况设置其⾳量条的位置,最后调⽤mDialog.show()显⽰出来。同时,发送⼀个延时消息MSG_TIMEOUT,这条延时消息⽣效时,将会关闭提⽰框。
接下来具体看⼀下VolumePanel在收到⾳量变化通知后都做了什么。
VolumePanel在MSG_VOLUME_CHANGED的消息处理函数中调⽤onVolumeChanged()函数,⽽不是直接在postVolumeChanged()函数中直接调⽤。这么做是有实际意义的。由于Android要求只能在创建控件的线程中对控件进⾏操作。postVolumeChanged()作为⼀个回调性质的函数,不能要求调⽤者位于哪个线程中。所以必须通过向Handler发送消息的⽅式,将后续的操作转移到指定的线程中。
下⾯再看⼀下onVolumeChanged()函数的实现:
protected void onVolumeChanged(int streamType, int flags) {
// 需要flags中包含AudioManager.FLAG_SHOW_UI 才会显⽰⾳量调节通知框
if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
synchronized (this) {
if (mActiveStreamType != streamType) {
reorderSliders(streamType); // 在Dialog⾥装载需要的StreamControl
}
onShowVolumeChanged(streamType, flags, null);
}
}
// 是否播出Tone⾳,注意有个⼩延迟
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
removeMessages(MSG_PLAY_SOUND);
sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
}
// 取消声⾳与振动的播放
if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
removeMessages(MSG_PLAY_SOUND);
removeMessages(MSG_VIBRATE);
onStopSounds();
}
// 开始安排回收资源
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
resetTimeout(); // 重置⾳量框超时关闭的时间
}
注意最后⼀个resetTimeout()的调⽤,其实它重新延时发送了MSG_TIMEOUT消息。当MSG_TIMEOUT消息⽣效时,mDialog将被关闭。
之后就是onShowVolumeChanged()了。这个函数负责为通知框的内容填充⾳量、图表等信息,然后再显⽰通知框(如果还没有显⽰)。
protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
int index = getStreamVolume(streamType);// 获取⾳量值
int max = getStreamMaxVolume(streamType); // 获取⾳量最⼤值,这两个将⽤来设置进度条
StreamControl sc = (streamType);
//在这个switch语句中,我们要根据每种流类型的特点进⾏各种调整。例如Music有时就需要更新它的图标,因为使⽤蓝⽛⽿机时的图标和平时的不⼀样,所以每⼀次都需要更新⼀下    switch (streamType) {
case AudioManager.STREAM_MUSIC: {
// Special case for when Bluetooth is active for music
if ((DevicesForStream(AudioManager.STREAM_MUSIC) &
(AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE);
} else {
setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE);
}
break;
}
...
}
if (sc != null) {
...
updateSliderProgress(sc, index); // 更新Seekbar的显⽰
final boolean muted = isMuted(streamType);
updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
...
updateSliderIcon(sc, muted); //更新stream_icon
}
}
if (!isShowing()) { // 如果对话框还没有显⽰
int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;
//⼀旦此通知框被显⽰,之后按下⾳量键都只能调节当前流类型的⾳量。直到通知框关闭时,重新调⽤forceVolumeControlStream(),并设置streamType为-1
if (stream != STREAM_MASTER) {
mAudioManager.forceVolumeControlStream(stream);
}
mDialog.show(); // 显⽰对话框
...
}
// Do a little vibrate if applicable (only when going into vibrate mode)
if ((streamType != STREAM_REMOTE_MUSIC) &&
((flags & AudioManager.FLAG_VIBRATE) != 0) &&
isNotificationOrRing(streamType) &&
sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);//稍微振动(仅当进⼊振动模式时)
}
...
}
看到updateSliderProgress()更新Seekbar⾳量。代码如下:
private void updateSliderProgress(StreamControl sc, int progress) {
final boolean isRinger = isNotificationOrRing(sc.streamType);
if (isRinger && RingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
progress = mLastRingerProgress;
}
if (progress < 0) {
progress = getStreamVolume(sc.streamType); // 获取⾳量值
}
sc.seekbarView.setProgress(progress);//设置⾳量条
if (isRinger) {
mLastRingerProgress = progress;
}
}
下⾯总结⼀下:
postVolumeChanged()是VolumePanel显⽰的⼊⼝。是通过VolumeUI.java⾥⾯调⽤mPanel.postVolumeChanged()⽅法进⼊的。
检查flags中是否有FLAG_SHOW_UI。
VolumePanel会在第⼀次被要求弹出时初始化其控件资源。
mDialog 加载指定流类型对应的StreamControl,也就是控件。
显⽰对话框并开始超时计时。
超时计时到达,关闭对话框。
以上就是本⽂关于Android系统⾳量条实例代码的全部内容,希望对⼤家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不⾜之处,欢迎留⾔指出。感谢朋友们对本站的⽀持!