Android系统音量条实例代码

最近在定制Android系统音量条,发现代码还是蛮多的,下面总结一下。

代码是基于5.1.1版本的。

系统音量条的代码是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java

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

先看看原生的音量条样式:

在代码中可以发现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系统音量条实例代码的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

您可能感兴趣的文章:

  • Android编程实现音量按钮添加监听事件的方法
  • android编程获取和设置系统铃声和音量大小的方法
  • Android 自定义SeekBar动态改变硬件音量大小实现和音量键的同步(推荐)
  • Android seekbar(自定义)控制音量同步更新
  • Android使用AudioManager修改系统音量的方法
  • Android 使用Vitamio打造自己的万能播放器(2)—— 手势控制亮度、音量、缩放
  • Android中自定义View实现圆环等待及相关的音量调节效果
  • Android原生音量控制实例详解
(0)

相关推荐

  • Android 自定义SeekBar动态改变硬件音量大小实现和音量键的同步(推荐)

    1,上图: 2,代码: MainActivity.Java package com.hero.zhaoq.seekbarchangeddemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObse

  • Android 使用Vitamio打造自己的万能播放器(2)—— 手势控制亮度、音量、缩放

    前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础. 系列 1.Android 使用Vitamio打造自己的万能播放器(1)--准备 正文 一.实现目标 1.1 亮度控制 模仿VPlayer界面: 1.2 声音控制 模仿VPlayer界面: 1.3 画面缩放 根据下面API提供画面的拉伸.剪切.100%.全屏     二.Vitamio API 介绍 VideoView 2.1 public void start() 开始播放 2.2 public void pause() 暂停播放

  • Android编程实现音量按钮添加监听事件的方法

    本文实例讲述了Android编程实现音量按钮添加监听事件的方法.分享给大家供大家参考,具体如下: 很多Android应用都应用到音量按钮,比如翻页,调整音乐声音大小等,但是如果没有对音量按钮进行监听,则无法达到预期的效果.如下代码,就是监听Android手机的音量按钮,开发者可以在相应的位置添加自己需要实现的功能. @Override public boolean onKeyDown (int keyCode, KeyEvent event) { // 获取手机当前音量值 int i = get

  • Android原生音量控制实例详解

    本文主要涉及AudioService.还是基于5.1.1版本的代码. AudioService.java文件位于/framework/base/media/java/android/media/下. 音量控制是AudioService最重要的功能之一.先总结一下: AudioService音量管理的核心是VolumeStreamState.它保存了一个流类型所有的音量信息. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的.所以进行音

  • Android中自定义View实现圆环等待及相关的音量调节效果

    圆环交替.等待效果 效果就这样,分析了一下,大概有这几个属性,两个颜色,一个速度,一个圆环的宽度. 自定View的几个步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure  4.重写onDraw 1.自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="firstColor" f

  • Android seekbar(自定义)控制音量同步更新

    Android seekbar控制音量同步更新 作为开发人员来讲,seekbar你一定会碰到,那么怎么自定义seekbar以及seekbar控制的音量怎么与系统的seekbar音量同步更新.今天就下拉菜单中添加SeekBar控制音量为例(Android 5.1系统)   一:自定义Seekbar 先来看一下所自定义的效果图: 首先来来看一下布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/androi

  • Android使用AudioManager修改系统音量的方法

    本文实例讲述了Android使用AudioManager修改系统音量的方法.分享给大家供大家参考,具体如下: 下面介绍几个AudioManager的几个音量调整方面的方法. 首先是得到AudioManager实例: AudioManager am=(AudioManager)getSystemService(Context.AUDIO_SERVICE); 调整音量方法有两种,一种是渐进式,即像手动按音量键一样,一步一步增加或减少,另一种是直接设置音量值. 1.渐进式 public void ad

  • android编程获取和设置系统铃声和音量大小的方法

    本文实例讲述了android编程获取和设置系统铃声和音量大小的方法.分享给大家供大家参考,具体如下: 通过程序获取android系统手机的铃声和音量.同样,设置铃声和音量的方法也很简单! 设置音量的方法也很简单,AudioManager提供了方法: public voidsetStreamVolume(intstreamType,intindex,intflags) 其中streamType有内置的常量,去文档里面就可以看到. JAVA代码: AudioManager mAudioManager

  • Android系统音量条实例代码

    最近在定制Android系统音量条,发现代码还是蛮多的,下面总结一下. 代码是基于5.1.1版本的. 系统音量条的代码是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java 布局文件是在/frameworks/base/packages/SystemUI/res/layout下. 先看看原生的音量条样式: 在代码中可以发现volume_dialog.xml这个文件,这个文件就是承载

  • Android自定义View实现带数字的进度条实例代码

    第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 第二步.自定义ProgressBar实现带数字的进度条 0.项目结构 如上图所示:library项目为自定义的带数字的进度条NumberProgressBar的具体实现,demo项目为示例项目以工程依赖的方式引用library项目,然后使用自定义的带数字的进度条NumberProgressBar来做展示 如上图所示:自定义的带数字的进度条的library项目的结构图 如上图所示:de

  • Android仿泡泡窗实现下拉菜单条实例代码

    功能描述:点击下拉按钮,显示出所有的条目,有删除和点击功能,点击后将条目显示. 注意:泡泡窗默认是没有焦点的.要让泡泡窗获取到焦点.假如listview的item中有Button,ImageButton,CheckBox等会强制获取焦点的view 此时,listview的item无法获取焦点,从而无法被点击 解决方法:给item的根布局增加以下属性 Android:descendantFocusability="blocksDescendants"设置之后,Button获取焦点,ite

  • Android自定义控件之可拖动控制的圆环控制条实例代码

    前几天收到这么一个需求,本来以为挺简单的,没想到最后发现实现起来还是有点小麻烦的,在这里小小的总结一下. 先看看下面这张需求的样图: 然后在看一下最终实现的效果图,可能是gif录制软件的问题,有一些浮影,忽略就好了: 首先要分析一下最核心的地方,如何获取到滑动距离对应的弧长,看图: p1是手指按下的点,很明显要想知道当前进度弧边的值,就是要求出角d的值. 以p为圆心点,atan(b)=Math.atan((-p.y)/(-p.x)); 所以角d的值为:Math.toDegrees(atan);

  • Webview实现android简单的浏览器实例代码

    WebView是Android中一个非常实用的组件,它和Safai.Chrome一样都是基于Webkit网页渲染引擎,可以通过加载HTML数据的方式便捷地展现软件的界面,下面通过本文给大家介绍Webview实现android简单的浏览器实例代码. 实现了浏览器的返回 前进 主页 退出 输入网址的功能 注释的很清楚啦 就不多说了 首先是布局文件 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&qu

  • JavaScript实现实时更新系统时间的实例代码

    一.Js代码 function getTime(){ str = "当前系统时间:" var p = document.getElementById("sy_time"); time = new Date(); year = time.getFullYear(); month = time.getMonth() + 1; day = time.getDate(); hour = time.getHours(); minutes = time.getMinutes()

  • iOS实现左右可滑动的选择条实例代码分享

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController @end RootViewController.m #import "RootViewController.h" //引入头文件 #import "SVSegmentedControl.h" @interface RootView

  • BootStrap创建响应式导航条实例代码

    首先你得引入bootstrap与jquery 推荐一个CDN:http://cdn.gbtags.com/index.html 然后就是开始编写HTML代码.如果你不想更改显示效果的话实际上CSS都免去写了2333 因为HTML代码比较多 这里分为三个部分 然后最后再上一份整体HTML代码 首先如上图所示的,实现这个效果需要了解bootstrap的以下几个组件 •导航条 •按钮 •表单 •下拉菜单 实际上以上几个组件的样式有很多.我们只需要了解一部分即可 如需了解更多的请转自http://www

  • BootStrap 导航条实例代码

    一.默认的导航条 制作默认的导航条,可分以下几步: 1.在ul里加上(ul class="nav navbar-nav")样式: 2.在ul外加一层div或nav(ps:HTML5新属性),并且添加样式(div class="navbar nabar-default"); <nav class="navbar navbar-default"> <ul class="nav navbar-nav"> &l

随机推荐