91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android系統音量條實例代碼

發布時間:2020-10-20 14:12:06 來源:腳本之家 閱讀:291 作者:他叫小黑 欄目:移動開發

最近在定制Android系統音量條,發現代碼還是蠻多的,下面總結一下。

代碼是基于5.1.1版本的。

系統音量條的代碼是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java

布局文件是在/frameworks/base/packages/SystemUI/res/layout下。

先看看原生的音量條樣式:

Android系統音量條實例代碼

Android系統音量條實例代碼

在代碼中可以發現volume_dialog.xml這個文件,這個文件就是承載音量條的布局了,在layout文件夾找到打開會發現這個布局很簡單,只是include了一個volume_panel

volume_panel布局包含了一個id叫slider_panel的FrameLayout和include了一個zen_mode_panel,顯然slider_panel后面會包含seekbar,看VolumePanel.java也會發現在代碼中加載了volume_panel_item.xml這個文件,一看,發現里面就包含了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) {
      ...
    }
  }

這幾個枚舉項組成了一個名為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 = seekBar.getTag();
      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 = mStreamControls.get(streamType);
    //在這個switch語句中,我們要根據每種流類型的特點進行各種調整。例如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;
      //一旦此通知框被顯示,之后按下音量鍵都只能調節當前流類型的音量。直到通知框關閉時,重新調用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) &&
        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);//設置音量條
    if (isRinger) {
      mLastRingerProgress = progress;
    }
  }

下面總結一下:

postVolumeChanged() 是VolumePanel顯示的入口。是通過VolumeUI.java里面調用mPanel.postVolumeChanged()方法進入的。

檢查flags中是否有FLAG_SHOW_UI。

VolumePanel會在第一次被要求彈出時初始化其控件資源。

mDialog 加載指定流類型對應的StreamControl,也就是控件。

顯示對話框并開始超時計時。

超時計時到達,關閉對話框。

以上就是本文關于Android系統音量條實例代碼的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

石首市| 芜湖县| 清丰县| 霸州市| 兴安县| 始兴县| 原阳县| 双流县| 和林格尔县| 安徽省| 杭州市| 前郭尔| 海晏县| 汕头市| 科技| 黄平县| 仙游县| 张家口市| 城口县| 固阳县| 上思县| 宜川县| 宁武县| 黔江区| 达孜县| 西畴县| 定结县| 安宁市| 聂荣县| 桑植县| 济宁市| 安西县| 临夏市| 凤山市| 尼玛县| 霍城县| 迁安市| 金塔县| 乌兰浩特市| 巨鹿县| 屏南县|