最近在定制Android系統(tǒng)音量條,發(fā)現(xiàn)代碼還是蠻多的,下面總結(jié)一下。
創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),興隆企業(yè)網(wǎng)站建設(shè),興隆品牌網(wǎng)站建設(shè),網(wǎng)站定制,興隆網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,興隆網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
代碼是基于5.1.1版本的。
系統(tǒng)音量條的代碼是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
布局文件是在/frameworks/base/packages/SystemUI/res/layout下。
先看看原生的音量條樣式:
在代碼中可以發(fā)現(xiàn)volume_dialog.xml這個文件,這個文件就是承載音量條的布局了,在layout文件夾找到打開會發(fā)現(xiàn)這個布局很簡單,只是include了一個volume_panel。
volume_panel布局包含了一個id叫slider_panel的FrameLayout和include了一個zen_mode_panel,顯然slider_panel后面會包含seekbar,看VolumePanel.java也會發(fā)現(xiàn)在代碼中加載了volume_panel_item.xml這個文件,一看,發(fā)現(xiàn)里面就包含了seekbar這個控件啦。另外zen_mode_panel是指勿擾模式。
在看這個布局文件的時候,你會看到android:clipChildren
這個屬性,它的作用:是否限制子View在其范圍內(nèi),我們將其值設(shè)置為false后那么當子控件的高度高于父控件時也會完全顯示,而不會被壓縮。默認為true。
若想某個控件不顯示,設(shè)置屬性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), // 這里省略了后面的幾個枚舉項的構(gòu)造參數(shù),這些與BluetoothSCOStream的內(nèi)容是一致的 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; // 是否顯示 //構(gòu)造函數(shù) StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) { ... } }
這幾個枚舉項組成了一個名為STREAM的數(shù)組,如下:
private static final StreamResources[] STREAMS = { StreamResources.BluetoothSCOStream, StreamResources.RingerStream, StreamResources.VoiceStream, StreamResources.MediaStream, StreamResources.NotificationStream, StreamResources.AlarmStream, StreamResources.MasterStream, StreamResources.RemoteStream };
VolumePanel將從這個STREAMS數(shù)組中獲取它所支持的流類型的相關(guān)資源。
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實例中保存了音量調(diào)節(jié)通知框中所需的所有控件。出于對運行效率的考慮,StreamControl實例也是每個流類型人手一份,和StreamResources實例形成一一對應(yīng)的關(guān)系。所有的StreamControl實例被保存在一個以流類型的值為鍵的SparseArray中,名為mStreamControls??梢栽赟treamControl的初始化函數(shù)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創(chuàng)建一個StreamControl // 下面將初始化sc的成員變量 ... sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); // 設(shè)置監(jiān)聽 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 = seekBar.getTag(); if (fromUser && tag instanceof StreamControl) { StreamControl sc = (StreamControl) tag; //設(shè)置音量 setStreamVolume(sc, progress,AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); } resetTimeout(); } ... };
這個初始化的工作并沒有在構(gòu)造函數(shù)中進行,而是在postVolumeChanged()、postRemoteVolumeChanged()、postMuteChanged()
函數(shù)中處理的。
VolumePanel保存了一個名為mDialog的Dialog實例,這就是通知框的本身了。每當有新的音量變化到來時,mDialog的內(nèi)容就會被替換為指定流類型對應(yīng)的StreamControl中所保存的控件,并且根據(jù)音量變化情況設(shè)置其音量條的位置,最后調(diào)用mDialog.show()顯示出來。同時,發(fā)送一個延時消息MSG_TIMEOUT,這條延時消息生效時,將會關(guān)閉提示框。
接下來具體看一下VolumePanel在收到音量變化通知后都做了什么。
VolumePanel在MSG_VOLUME_CHANGED的消息處理函數(shù)中調(diào)用onVolumeChanged()
函數(shù),而不是直接在postVolumeChanged()
函數(shù)中直接調(diào)用。這么做是有實際意義的。由于Android要求只能在創(chuàng)建控件的線程中對控件進行操作。postVolumeChanged()
作為一個回調(diào)性質(zhì)的函數(shù),不能要求調(diào)用者位于哪個線程中。所以必須通過向Handler發(fā)送消息的方式,將后續(xù)的操作轉(zhuǎn)移到指定的線程中。
下面再看一下onVolumeChanged()函數(shù)的實現(xiàn):
protected void onVolumeChanged(int streamType, int flags) { // 需要flags中包含AudioManager.FLAG_SHOW_UI 才會顯示音量調(diào)節(jié)通知框 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(); // 重置音量框超時關(guān)閉的時間 }
注意最后一個resetTimeout()
的調(diào)用,其實它重新延時發(fā)送了MSG_TIMEOUT消息。當MSG_TIMEOUT消息生效時,mDialog將被關(guān)閉。
之后就是onShowVolumeChanged()
了。這個函數(shù)負責(zé)為通知框的內(nèi)容填充音量、圖表等信息,然后再顯示通知框(如果還沒有顯示)。
protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) { int index = getStreamVolume(streamType);// 獲取音量值 int max = getStreamMaxVolume(streamType); // 獲取音量最大值,這兩個將用來設(shè)置進度條 StreamControl sc = mStreamControls.get(streamType); //在這個switch語句中,我們要根據(jù)每種流類型的特點進行各種調(diào)整。例如Music有時就需要更新它的圖標,因為使用藍牙耳機時的圖標和平時的不一樣,所以每一次都需要更新一下 switch (streamType) { case AudioManager.STREAM_MUSIC: { // Special case for when Bluetooth is active for music if ((mAudioManager.getDevicesForStream(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; //一旦此通知框被顯示,之后按下音量鍵都只能調(diào)節(jié)當前流類型的音量。直到通知框關(guān)閉時,重新調(diào)用forceVolumeControlStream(),并設(shè)置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) && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);//稍微振動(僅當進入振動模式時) } ... }
看到updateSliderProgress()
更新Seekbar音量。代碼如下:
private void updateSliderProgress(StreamControl sc, int progress) { final boolean isRinger = isNotificationOrRing(sc.streamType); if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { progress = mLastRingerProgress; } if (progress < 0) { progress = getStreamVolume(sc.streamType); // 獲取音量值 } sc.seekbarView.setProgress(progress);//設(shè)置音量條 if (isRinger) { mLastRingerProgress = progress; } }
下面總結(jié)一下:
postVolumeChanged()
是VolumePanel顯示的入口。是通過VolumeUI.java里面調(diào)用mPanel.postVolumeChanged()
方法進入的。
檢查flags中是否有FLAG_SHOW_UI。
VolumePanel會在第一次被要求彈出時初始化其控件資源。
mDialog 加載指定流類型對應(yīng)的StreamControl,也就是控件。
顯示對話框并開始超時計時。
超時計時到達,關(guān)閉對話框。
以上就是本文關(guān)于Android系統(tǒng)音量條實例代碼的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!