RK3576-Android15实现SystemUI下拉框新增音量条功能

在RK平台-Android15下拉框,新增 音量控制条


前言

在Android15 平台定制化产品中的下拉框中新增音量条调节功能,市场上部分教育类产品见到过这样的使用需求场景、手机都是有的。实际的默认的Android源码是没有这个功能的,自己来实现下。

一、 需求

在RK3568 Android15 产品上面实现 SystemUI 下拉框新增音量条功能。

需要实现基本效果图如下:
在这里插入图片描述

二、参考资料

Android12_SystemUI下拉框新增音量控制条
Android13_SystemUI下拉框新增音量控制条

提醒:

  • 上面是之前自己总结 在MTK平台 Android12 Android13 上面实现的 下拉框增加音量条的需求,已经帮助很多人在不同芯片平台不同Android 版本上面实现功能。
  • 这里 以RK3576 平台,Android15 上面再次实现功能
  • 自己经验来说:Android12/Android13 中的实现方式对于Android15 来说也具有极大的参考价值,其中架构思想依然没有变化,很多类更换了或者新增部分方法,都可以参考借鉴,并稍微修改实现即可。

三、更新文件-实现方案

更新文件-新增文件

修改的文件
frameworks/base/packages/SystemUI/AndroidManifest.xml
frameworks/base/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
frameworks/base/packages/SystemUI/AndroidManifest.xml

新增的文件
frameworks/base/packages/SystemUI/res/drawable-hdpi/icon_volume_test.png
frameworks/base/packages/SystemUI/res/drawable-xhdpi/icon_volume_test.png
frameworks/base/packages/SystemUI/res/drawable-xxhdpi/icon_volume_test.png
frameworks/base/packages/SystemUI/res/drawable-xxxhdpi/icon_volume_test.png
frameworks/base/packages/SystemUI/res/drawable/volume_progress_drawable.xml
frameworks/base/packages/SystemUI/res/drawable/volume_progress_full_drawable.xml
frameworks/base/packages/SystemUI/res/layout/quick_settings_volume_dialog.xml
frameworks/base/packages/SystemUI/src/com/android/systemui/settings/volume/
frameworks/base/packages/SystemUI/src/com/android/systemui/util/ApplicationContextProvider.kt
frameworks/base/packages/SystemUI/src/com/android/systemui/util/ContextProvider.java
frameworks/base/packages/SystemUI/src/com/android/systemui/util/SoundUtils.kt
 
frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeChangeObserver.java
frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeController.java
frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeSliderController.java
frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeSliderView.java


在这里插入图片描述

实现方案

思路

如果是第一次集成,强烈建议优先看一看之前的篇章,会有更好的思路,特别是Android13 集成,它的代码和Android15 差不多,如下:
Android12_SystemUI下拉框新增音量控制条
Android13_SystemUI下拉框新增音量控制条

集成分几个步骤和要解决的问题:

  • 1) 思想:完全copy 已有的亮度控制条逻辑实现,熟悉、理解亮度条 实现原理、架构、步骤。再进一步去实现需求,添加音量条功能
  • 2) 音量条UI创建,知道如何创建音量条、逻辑、业务搞清楚
  • 3) 音量条UI添加到QsPanel 里面去
  • 4) 添加的UI音量条如何受控
  • 5) 高版本源码、不分架构变化 存在音量条的监听、控制逻辑需要自行实现了

具体实现

再次建议,再编码之前,建议看一下之前的文章Android12 / Android13 上面的指导,同步看一下 文章后面的架构分析,方便理解、高效编码。

1、UI定义部分-VolumeSliderView-ToggleSeekBar

VolumeSliderView 路径:frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeSliderView.java
看类定义,就是一个 FrameLayout,它是用来包裹音量条SeekBar 的,看类定义:用来展示和操作SeekBar的

看左侧的方法不都是用来操作 SeekBar 的嘛:它本身持有 ToggleSeekBar
在这里插入图片描述
在这里插入图片描述

ToggleSeekBar 路径:frameworks/base/packages/SystemUI/src/com/android/systemui/brightness/ToggleSeekBar.java
这个就是一个进度条,这里用来亮度显示的,我们可以直接用它来作为音量的进度控制条,它就是一个SeekBar
在这里插入图片描述

2、接口定义-ToggleSlider

frameworks/base/packages/SystemUI/src/com/android/systemui/brightness/ToggleSlider.java
这个就是一个进度条解控,这里用来定义亮度接口控制和回调的,我们可以直接用它来作为音量的进度控制条的控制接口定义,便于更好控制SeekBar
在这里插入图片描述

3、UI资源定义

这里完全参考亮度条取名,直接复制改名,然后:更为为自己的UI资源
在这里插入图片描述

3、添加UI到QSPanel -QSPanelController- VolumeSliderController - VolumeController
ViewModel 类:VolumeSliderController

直接看类定义:这个类就是一个ViewModel

/**
 * {@code ViewController} for a {@code VolumeSliderView}
 *
 * This class allows to control the views of a {@code VolumeSliderView} and get callbacks
 * when the views change value. It also propagates and manipulates another {@link ToggleSlider} as a
 * mirror.
 *
 * @see VolumeSliderController
 */
public class VolumeSliderController extends ViewController<VolumeSliderView> implements ToggleSlider {
  

工厂模式,加载布局:
在这里插入图片描述

布局绑定设置监听事件:
在这里插入图片描述

对外音量控制类 VolumeController

简要说明这个类的作用:

  • 释放对外控制音量条接口、对SeekBar 的回调
  • 对内包装一层,封装音量调ViewModel, 实现对音量条控制的传递。是一个实际意义上的控制器。
  • 不仅仅持有控制SeekBar 的方法接口,并传递到内部控制,动画也是在这个控制层实现的。

看类定义,就是一个实现 SeekBar 控制器接口的类:

    这里特别强调一下: 内部持有 private final ToggleSlider mControl; 这个特别重要:持有 SeekBar 控制器接口,如果这个接口是ViewModel 是Toggleslider 实现类。这就实现了
接口引用,封装了一层ViewModel 的控制器,我们再看看 ViewModel VolumeSliderController类的 定义:public class VolumeSliderController extends ViewController<VolumeSliderView> implements ToggleSlider {

在这里插入图片描述

**看类的创建,工厂模式 **
如,我们上面分析,传递的就是一个 SeekBar 的控制器类 ToggleSlider,属于接口传递范畴:
在这里插入图片描述

QSPanelController-QS面板控制器

这个类,单从 音量控制部分,主要做了以下几个事情:如上部分分析

  • 绑定音量条View 到ViewModel 类 VolumeSliderController
  • 绑定音量控制器 VolumeController和ViewModel 类 VolumeSliderController
  • 初始化、设置监听等

在构造方法中:绑定音量条View 到ViewModel 类 VolumeSliderController , 绑定音量控制器 VolumeController和ViewModel 类 VolumeSliderController, 如下:

         // modify by fangchen start
        Log.d(TAG,"============QSPanelController  gouzao  ====")
        mVolumeSliderController = mVolumeSliderFactory.create(getContext(), mView);
        mView.setVolumeView(mVolumeSliderController.getRootView());
        mVolumeController = volumeControllerFactory.create(mVolumeSliderController);

        // modify by fangchen end

viewModel init 操作处理View生命周期:
这里有一个init 操作,ViwModel 里面机制,如下: mVolumeSliderController.init()

  @Override
    public void onInit() {
        super.onInit();
        mMediaHost.setExpansion(MediaHostState.EXPANDED);
        mMediaHost.setShowsOnlyActiveMedia(false);
        mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
        mQsCustomizerController.init();
        mBrightnessSliderController.init();

        // modify by fangchen start
        Log.d(TAG,"============onInit  mVolumeSliderController.init  ====")
        mVolumeSliderController.init();
        // modify by fangchen end

    }

在这里插入图片描述

添加音量条到QS 控制面板 QSPanel

前面讲的几个都是 控制相关的类,这里搞清楚 音量调、进度条 到底是如何添加到 QS 快捷面板QSPanel 里面去的。
QSPanel 类定义,如下:它就是我们属性的快捷面板,下拉或者展开时候的一个View ,在之前的 SystemUI 专栏 其实已经对它很熟悉了。

/** View that represents the quick settings tile panel (when expanded/pulled down). **/
public class QSPanel extends LinearLayout implements Tunable {

参考添加亮度条的案例,原有的逻辑,如下:
在这里插入图片描述

我们参考后,自己写一个音量条的UI,添加进去不就可以了嘛:这里注意添加有顺序 index 的,亮度调为0 放在第一位,音量条设置为1, 放在第二个位置即可: addView(view, 1);
在这里插入图片描述

问题:既然我们添加了音量条VolumeView 到 QSPanel 中,那这个方法到底在哪里调用的:继续看 QSPanel 控制器 QSPanelController,如下代码,再次分析:

在这里插入图片描述

         // modify by fangchen start
        Log.d(TAG,"============QSPanelController  gouzao  ====")
        mVolumeSliderController = mVolumeSliderFactory.create(getContext(), mView);
        mView.setVolumeView(mVolumeSliderController.getRootView());
        mVolumeController = volumeControllerFactory.create(mVolumeSliderController);

        // modify by fangchen end

做了以下几个事情:其中就是创建亮度条,放到QSPanel 对应位置中去

  • mVolumeSliderFactory.create(getContext(), mView): mVolumeSliderFactory 工厂绑定父View QSPanel View,并同步加载音量条
  • mView.setVolumeView(mVolumeSliderController.getRootView());QsPanel设置音量条加载到QSPanel view 中,放到对应位置中去
  • volumeControllerFactory.create(mVolumeSliderController);:音量控制器和音量ViewModel 之间的绑定
4、音量控制

    前面分析了UI 、资源、各种控制器Controller,那么音量进度条到底怎么控制音量,下发控制的。也就是说 拖动音量进度条,如何控制音量。

其实在分析 VolumeSliderController 这个ViewModel 已经讲过 设置监听事件,再次展示如下:

在这里插入图片描述
但是在ViewModel 中并没有直接处理,这个它做好了本分工作只处理View Data 的数据绑定工作,控制类它不处理:
在这里插入图片描述
继续看这个回调接口:mListener
在这里插入图片描述
如下:它就是SeekBar 接口 ToggleSlider 定义的一个方法,
在这里插入图片描述

那 这个Listener 到底是谁? 如下: 不就是 VolumeController 嘛,如下定义及回调:在控制器中处理控制业务,控制音量业务。
在这里插入图片描述在这里插入图片描述

5、音量监听 - VolumeChangeObserver

  上面分析了基本所有的业务:UI、控制、模型、封装、绑定、事件,实测有个问题,在下拉框展示的时候,去控制音量,那么你的音量条需要自动跟随音量变化来变化UI的。
如下,操控右边音量条情况下,下拉框中的音量条应该同步跟随变化才行。
在这里插入图片描述

以前Android12 机制自带,Android13 、Android14、 Android15 需要自己写了。 所以我们需要自己监听音量变化来更新UI。

源码如下:

package com.android.systemui.settings.volume;

import android.content.Context;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.os.Handler;
import android.util.Log;
import com.android.systemui.util.SoundUtils;

 
public class VolumeChangeObserver extends ContentObserver {
    private Context mContext;
   // private VolumeControllerListener mVolumeControllerListener;
    private VolumeController mVolumeControllerListener;

    private int toSetVolume=0;
    private   final String TAG = "VolumeChangeObserver";
    public VolumeChangeObserver(Context context, VolumeController volumeControllerListener,  Handler handler) {
        super(handler);
        mContext = context;
        mVolumeControllerListener=volumeControllerListener;
     }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);

         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        //Log.d(TAG, "System volume setting changed. Current music volume: " + currentVolume);
         int nowVoiceValue = SoundUtils.INSTANCE.get100CurrentVolume();
		 
		 Log.d(TAG," onChange===toSetVolume:"+toSetVolume + "    nowVoiceValue:"+nowVoiceValue);
         		
				
				

      
		  
		     if (toSetVolume==0&&nowVoiceValue!=0) {
				 
				  Log.d(TAG,"==============toSetVolume==0&&nowVoiceValue!=0       return ===========" );
			  
			         return;
            
                 }
		  
		  
        // tognzhi huidiaoqi
         if(mVolumeControllerListener != null) {
            mVolumeControllerListener.onVolumeChanged(currentVolume);
        }
		
    }
	
	
	public void  setVoice(int value){
		
		
		// this value is 100 kedu zhi 
		toSetVolume=value;
		Log.d(TAG, "setVoice  volume: " + toSetVolume);

	}
	
	
}

在音量控制器VolumeController 中去监听、回调音量变化 并 接收回调,动态更新

    mVolumeObserver = new VolumeChangeObserver(mContext, this, new Handler());
       
        ContextProvider.get().getContext().getContentResolver().registerContentObserver(
                Settings.System.CONTENT_URI,  
                true, 
                mVolumeObserver
        );



   public void onVolumeChanged(int newVolume) {
         Log.d(TAG," ===============onVolumeChanged====newVolume:"+newVolume);
		 mBackgroundHandler.post(mUpdateSliderRunnable);
     }


6、CentralSurfacesImpl 一个集成化的界面管理中心 - 亮度初始化

CentralSurfacesImpl Android SystemUI 的核心控制类之一,它接管了原 StatusBar.java 的职责,负责管理状态栏、导航栏、锁屏、快速设置面板等系统界面的整体框架

看类定义:

/**
 * A class handling initialization and coordination between some of the key central surfaces in
 * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
 *
 * This class is not our ideal architecture because it doesn't enforce much isolation between these
 * three mostly disparate surfaces. In an ideal world, this class would not exist. Instead, we would
 * break it up into three modules -- one for each of those three surfaces -- and we would define any
 * APIs that are needed for these surfaces to communicate with each other when necessary.
 *
 * <b>If at all possible, please avoid adding additional code to this monstrous class! Our goal is
 * to break up this class into many small classes, and any code added here will slow down that goal.
 * </b>
 *
 * Note that ActivityStarter logic here is deprecated and should be added here as well as
 * {@link ActivityStarterImpl}
 */
@SysUISingleton
public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
  • 一个处理系统UI中若干核心界面初始化与协调的类:通知面板、锁屏界面以及状态栏。

  • 此类并非我们理想中的架构,因为它未能在三个差异显著的界面之间强制实现足够的隔离。在理想情况下,此类本不应存在。取而代之的是,我们应将其拆分为三个独立模块——每个界面对应一个模块——并定义这些界面在必要时相互通信所需的接口。

  • 如有可能,请避免向这个庞大的类添加更多代码!我们的目标是将此类拆分为多个小型类,任何在此新增的代码都将延缓这一目标的实现。

构造音量条的VolumeSliderController ,参考亮度条, VolumeSliderController.Factory volumeSliderFactory,,并在构造方法中传递 对象
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

核心职责与继承关系

CentralSurfacesImpl 是一个具体实现类,其核心定位和结构如下:

方面说明
继承关系继承自 CoreStartable 类,并实现了 CentralSurfaces 接口。
核心职责作为SystemUI的“中央控制器”,负责协调和初始化多个关键UI组件。
替代关系在Android 12及之后的版本中,它取代了旧的 StatusBar.java,成为管理状态栏相关功能的主类。
关键功能与源码解析

其核心工作集中在makeStatusBarView()方法中。下表梳理了它在初始化时创建的几个主要子系统:

功能模块关键方法/操作作用与说明
状态栏视图makeStatusBarView()整个初始化过程的入口,构建状态栏的根视图。
快速设置(QS)面板在makeStatusBarView()中初始化负责下拉状态栏后看到的快捷开关面板。通过 FragmentHostManager 加载 QSFragment。
导航栏/任务栏createNavigationBar()创建屏幕底部的导航栏。在大屏设备(如平板)上,会转换为任务栏(Taskbar),这是Android 12+的一个重要变化。
手势导航系统通过TaskbarDelegate及EdgeBackGestureHandler间接管理负责处理边缘返回等手势操作,与任务栏的显示和交互紧密相关。
代码分析要点

在阅读 CentralSurfacesImpl 源码时,可以重点关注以下几点:

  • 依赖注入 (Dagger):该类及它初始化的组件(如QSTileHost)大量使用 @Inject 注解,这是现代SystemUI管理复杂依赖关系的典型方式。

  • 插件化与可扩展性:通过 ExtensionController、PluginManager 等机制,系统允许动态更换部分实现(如QS面板),提高了可定制性。

  • 状态与通信:它通过 CommandQueue、StatusBarStateController 等与系统其他部分(如WindowManager)通信,并监听状态变化(如锁屏、电量)来更新UI。

四、源码架构分析

这里仅列举几个类的几个作用,其它分析如上已经分析了部分,实际分析还得自己去体会。

这里先列举一下几个类的关键作用:

源码作用
QSPanel快捷面板UI:亮度条和音量条就在这个UI面板中显示
QSPanelController面板控制器:添加亮度条、音量条UI
CentralSurfacesImpl初始化所有组件、工厂
Classifier定义抽象的:触摸、传感事件
AndroidManifest配置四大组件:这里配置我们自己定义的 ContextProvider 、ApplicationContextProvider
quick_settings_volume_dialog音量条布局
volume_progress_drawable、volume_progress_full_drawable音量条背景
SoundUtils自定义音量工具类
VolumeSliderView包裹音量条SeekBar 的 UI组件 FrameLayout
VolumeSliderController音量条控制器 ,就是一个ViewModel
VolumeController音量条控制器,封装了VolumeSliderController 分层思想
VolumeChangeObserver音量监听,其它方式设置音量时候 同步SystemUI快捷面板中的音量条

总结

  • 在Android15 ,RK 平台上面实现 SystemUI 下拉框新增亮度条的功能。 实际可以参考之前文章
  • 务必理解 SystemUI基本架构、熟悉、了解 亮度条渲染控制操作,参考后再实现音量条操作
  • 根据各大网友实际反馈的集成小问题,都是没有理解类、架构、细节点未注意导致,遇到问题建议自己差错
  • Android15 和 Android12/13 集成,在类定义、接口 有少许变化,实际集成肯定需要自己去改就行了。
内容概要:本文介绍了一种基于蒙特卡洛模拟和拉格朗日优化方法的电动汽车充电站有序充电调度策略,重点针对分时电价机制下的分散式优化问题。通过Matlab代码实现,构建了考虑用户充电需求、电网负荷平衡及电价波动的数学模【电动汽车充电站有序充电调度的分散式优化】基于蒙特卡诺和拉格朗日的电动汽车优化调度(分时电价调度)(Matlab代码实现)型,采用拉格朗日乘子法处理约束条件,结合蒙特卡洛方法模拟大量电动汽车的随机充电行为,实现对充电功率和时间的优化分配,旨在降低用户充电成本、平抑电网峰谷差并提升充电站运营效率。该方法体现了智能优化算法在电力系统调度中的实际应用价值。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事新能源汽车、智能电网相关领域的工程技术人员。; 使用场景及目标:①研究电动汽车有序充电调度策略的设计与仿真;②学习蒙特卡洛模拟与拉格朗日优化在能源系统中的联合应用;③掌握基于分时电价的需求响应优化建模方法;④为微电网、充电站运营管理提供技术支持和决策参考。; 阅读建议:建议读者结合Matlab代码深入理解算法实现细节,重点关注目标函数构建、约束条件处理及优化求解过程,可尝试调整参数设置以观察不同场景下的调度效果,进一步拓展至多目标优化或多类型负荷协调调度的研究。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值