国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android系統(tǒng)音量條實(shí)例代碼

2019-12-12 01:10:16
字體:
供稿:網(wǎng)友

最近在定制Android系統(tǒng)音量條,發(fā)現(xiàn)代碼還是蠻多的,下面總結(jié)一下。

代碼是基于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這個(gè)文件,這個(gè)文件就是承載音量條的布局了,在layout文件夾找到打開會(huì)發(fā)現(xiàn)這個(gè)布局很簡(jiǎn)單,只是include了一個(gè)volume_panel

volume_panel布局包含了一個(gè)id叫slider_panel的FrameLayout和include了一個(gè)zen_mode_panel,顯然slider_panel后面會(huì)包含seekbar,看VolumePanel.java也會(huì)發(fā)現(xiàn)在代碼中加載了volume_panel_item.xml這個(gè)文件,一看,發(fā)現(xiàn)里面就包含了seekbar這個(gè)控件啦。另外zen_mode_panel是指勿擾模式。

在看這個(gè)布局文件的時(shí)候,你會(huì)看到android:clipChildren這個(gè)屬性,它的作用:是否限制子View在其范圍內(nèi),我們將其值設(shè)置為false后那么當(dāng)子控件的高度高于父控件時(shí)也會(huì)完全顯示,而不會(huì)被壓縮。默認(rèn)為true。

若想某個(gè)控件不顯示,設(shè)置屬性android:visibility=”gone”就好了。

看完布局,下面就主要看VolumePanel.java這個(gè)文件了。

VolumePanel下定義了兩個(gè)重要的子類型,分別是StreamResources和StreamControl。StreamResources實(shí)際上是一個(gè)枚舉,它的每一個(gè)可用元素保存了一個(gè)流類型的通知框所需要的各種資源,如圖標(biāo)、提示文字等。StreamResources的定義就像下面這樣:

  private enum StreamResources {    BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,        R.string.volume_icon_description_bluetooth,        IC_AUDIO_BT,        IC_AUDIO_BT_MUTE,        false),    // 這里省略了后面的幾個(gè)枚舉項(xiàng)的構(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; // 圖標(biāo)    int iconMuteRes; // 靜音圖標(biāo)    // 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) {      ...    }  }

這幾個(gè)枚舉項(xiàng)組成了一個(gè)名為STREAM的數(shù)組,如下:

  private static final StreamResources[] STREAMS = {    StreamResources.BluetoothSCOStream,    StreamResources.RingerStream,    StreamResources.VoiceStream,    StreamResources.MediaStream,    StreamResources.NotificationStream,    StreamResources.AlarmStream,    StreamResources.MasterStream,    StreamResources.RemoteStream  };

VolumePanel將從這個(gè)STREAMS數(shù)組中獲取它所支持的流類型的相關(guān)資源。

StreamControl類則保存了一個(gè)流類型的通知框所需要顯示的控件,其定義如下:

  /** 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實(shí)例中保存了音量調(diào)節(jié)通知框中所需的所有控件。出于對(duì)運(yùn)行效率的考慮,StreamControl實(shí)例也是每個(gè)流類型人手一份,和StreamResources實(shí)例形成一一對(duì)應(yīng)的關(guān)系。所有的StreamControl實(shí)例被保存在一個(gè)以流類型的值為鍵的SparseArray中,名為mStreamControls。可以在StreamControl的初始化函數(shù)createSliders()中看到。

  private void createSliders() {    ...    // 遍歷STREAM中所有的StreamResources實(shí)例    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)建一個(gè)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è)初始化的工作并沒有在構(gòu)造函數(shù)中進(jìn)行,而是在postVolumeChanged()、postRemoteVolumeChanged()、postMuteChanged()函數(shù)中處理的。

VolumePanel保存了一個(gè)名為mDialog的Dialog實(shí)例,這就是通知框的本身了。每當(dāng)有新的音量變化到來時(shí),mDialog的內(nèi)容就會(huì)被替換為指定流類型對(duì)應(yīng)的StreamControl中所保存的控件,并且根據(jù)音量變化情況設(shè)置其音量條的位置,最后調(diào)用mDialog.show()顯示出來。同時(shí),發(fā)送一個(gè)延時(shí)消息MSG_TIMEOUT,這條延時(shí)消息生效時(shí),將會(huì)關(guān)閉提示框。

接下來具體看一下VolumePanel在收到音量變化通知后都做了什么。

VolumePanel在MSG_VOLUME_CHANGED的消息處理函數(shù)中調(diào)用onVolumeChanged()函數(shù),而不是直接在postVolumeChanged()函數(shù)中直接調(diào)用。這么做是有實(shí)際意義的。由于Android要求只能在創(chuàng)建控件的線程中對(duì)控件進(jìn)行操作。postVolumeChanged()作為一個(gè)回調(diào)性質(zhì)的函數(shù),不能要求調(diào)用者位于哪個(gè)線程中。所以必須通過向Handler發(fā)送消息的方式,將后續(xù)的操作轉(zhuǎn)移到指定的線程中。

下面再看一下onVolumeChanged()函數(shù)的實(shí)現(xiàn):

    protected void onVolumeChanged(int streamType, int flags) {    // 需要flags中包含AudioManager.FLAG_SHOW_UI 才會(huì)顯示音量調(diào)節(jié)通知框    if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {      synchronized (this) {        if (mActiveStreamType != streamType) {          reorderSliders(streamType); // 在Dialog里裝載需要的StreamControl        }        onShowVolumeChanged(streamType, flags, null);      }    }    // 是否播出Tone音,注意有個(gè)小延遲    if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {      removeMessages(MSG_PLAY_SOUND);      sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);    }    // 取消聲音與振動(dòng)的播放    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(); // 重置音量框超時(shí)關(guān)閉的時(shí)間  }

注意最后一個(gè)resetTimeout()的調(diào)用,其實(shí)它重新延時(shí)發(fā)送了MSG_TIMEOUT消息。當(dāng)MSG_TIMEOUT消息生效時(shí),mDialog將被關(guān)閉。

之后就是onShowVolumeChanged()了。這個(gè)函數(shù)負(fù)責(zé)為通知框的內(nèi)容填充音量、圖表等信息,然后再顯示通知框(如果還沒有顯示)。

  protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {    int index = getStreamVolume(streamType);// 獲取音量值    int max = getStreamMaxVolume(streamType); // 獲取音量最大值,這兩個(gè)將用來設(shè)置進(jìn)度條    StreamControl sc = mStreamControls.get(streamType);    //在這個(gè)switch語句中,我們要根據(jù)每種流類型的特點(diǎn)進(jìn)行各種調(diào)整。例如Music有時(shí)就需要更新它的圖標(biāo),因?yàn)槭褂盟{(lán)牙耳機(jī)時(shí)的圖標(biāo)和平時(shí)的不一樣,所以每一次都需要更新一下    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()) { // 如果對(duì)話框還沒有顯示      int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;      //一旦此通知框被顯示,之后按下音量鍵都只能調(diào)節(jié)當(dāng)前流類型的音量。直到通知框關(guān)閉時(shí),重新調(diào)用forceVolumeControlStream(),并設(shè)置streamType為-1      if (stream != STREAM_MASTER) {        mAudioManager.forceVolumeControlStream(stream);      }      mDialog.show(); // 顯示對(duì)話框      ...    }    // 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);//稍微振動(dòng)(僅當(dāng)進(jìn)入振動(dòng)模式時(shí))    }  ...  }

看到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()方法進(jìn)入的。

檢查flags中是否有FLAG_SHOW_UI。

VolumePanel會(huì)在第一次被要求彈出時(shí)初始化其控件資源。

mDialog 加載指定流類型對(duì)應(yīng)的StreamControl,也就是控件。

顯示對(duì)話框并開始超時(shí)計(jì)時(shí)。

超時(shí)計(jì)時(shí)到達(dá),關(guān)閉對(duì)話框。

以上就是本文關(guān)于Android系統(tǒng)音量條實(shí)例代碼的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 五华县| 泰州市| 桐柏县| 珲春市| 恩平市| 临高县| 宁波市| 武平县| 尼勒克县| 阿坝县| 胶州市| 新兴县| 抚宁县| 福建省| 恩施市| 密山市| 阜平县| 和政县| 竹山县| 巴林右旗| 漠河县| 南城县| 通道| 广安市| 清徐县| 奉新县| 抚州市| 郎溪县| 湘潭市| 合山市| 泌阳县| 阳山县| 祁东县| 洛阳市| 恩施市| 民勤县| 庄河市| 兖州市| 大关县| 习水县| 紫云|