Android12_SystemUI下拉框新增音量控制条

Android产品下拉框一直只有亮度条没有音量控制条。 为了方便控制音量,普遍都是底部导航栏添加音量加减按钮,在Android10以后,大家普遍用上了手势导航,去掉底部导航栏。 目前需要再下拉框中可以直接控制音量。


前言

在Android12平台,QS 面板上,亮度控制条下面添加音量条,方便控制音量。

需求及效果

Android12 版本

  1. RK和MTK平台在下拉框QS面板中,亮度条的下方新增音量条控制器 亮度条长按不隐藏QS面板
  2. 亮度条长按不隐藏QS面板

在这里插入图片描述

在这里插入图片描述

基础必备

SystemUI 相关知识,务必做到基本流程了解、概念、架构、布局,方便实现基础功能和理解业务修改。
参考资料:截屏功能添加中的SystemUI基础描述

#修改说明
详细说明如下,参考修改文件和新增文件即可完成功能,实现需求

修改文件:

vendor/mediatek/proprietary/packages/apps/SystemUI/AndroidManifest.xml
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/classifier/Classifier.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/qs/QSAnimator.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/qs/QSPanel.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/qs/QSPanelController.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java

`

## 新增文件:

```java
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable-hdpi/icon_volume_test.png
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable-mdpi/icon_volume_test.png
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable-xhdpi/icon_volume_test.png
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable-xxhdpi/icon_volume_test.png
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable-xxxhdpi/icon_volume_test.png
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable/ic_volume.xml
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable/volume_progress_drawable.xml
vendor/mediatek/proprietary/packages/apps/SystemUI/res/drawable/volume_progress_full_drawable.xml
vendor/mediatek/proprietary/packages/apps/SystemUI/res/layout/quick_settings_volume_dialog.xml
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/volume/
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/util/ApplicationContextProvider.kt
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/util/ContextProvider.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/util/SoundUtils.kt

修改说明

修改说明详细文档,方便理解

去掉长按亮度条不隐藏QSPanel

  com.android.systemui.statusbar.policy.BrightnessMirrorController  亮度条镜控制器
	com.android.systemui.settings.brightness.BrightnessSliderSeekBar.OnSeekBarChangeListener回调方法中
	屏蔽mirrorController 的相关回调控制
	具体代码如下:
	 private final SeekBar.OnSeekBarChangeListener mSeekListener =
            new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (mListener != null) {
                mListener.onChanged(mTracking, progress, false);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            mTracking = true;

            if (mListener != null) {
                mListener.onChanged(mTracking, getValue(), false);
            }
            /*if (mMirrorController != null) {
                mMirrorController.showMirror();
                mMirrorController.setLocationAndSize(mView);
            }*/
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            mTracking = false;
            if (mListener != null) {
                mListener.onChanged(mTracking, getValue(), true);
            }
            /*if (mMirrorController != null) {
                mMirrorController.hideMirror();
            }*/
        }
    };

新增音量进度条到QS面板,放到亮度进度条下方

实现方案,参考源码中亮度条实现的方式实现

几个相关类引入

com.android.systemui.settings.brightness 包下

     BrightnessController.java   :对外的控制器 BrightnessController implements ToggleSlider.Listener   
	 BrightnessSlider.java       : 本质上也是一个View,带了ViewController控制器,extends    ViewController<BrightnessSliderView> implements ToggleSlider
	 ToggleSlider.java           : 定义进度条控制器的接口,如:进度条变化回调 onChanged、setMax、getMax、getValue、setValue
     BrightnessDialog.java       :显示音量的Activity,里面加载的是Dialog,源码暂未使用  
	 BrightnessSliderView.java   :SeekBar的根布局文件,包裹ToggleSeekBarView,本质是一个FrameLayout 布局
	 ToggleSeekBar.java          :SeekBar 

简要对部分类的简单分析

BrightnessSlider

中的Factory类,来构造BrightnessSlider, 这里面加载了布局 quick_settings_brightness_dialog,
在上面已经描述 这个BrightnessSlider 本质就是一个带控制器ViewController的view

 public static class Factory {

        private final FalsingManager mFalsingManager;

        @Inject
        public Factory(FalsingManager falsingManager) {
            mFalsingManager = falsingManager;
        }

        /**
         * Creates the view hierarchy and controller
         *
         * @param context a {@link Context} to inflate the hierarchy
         * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
         *                 hierarchy will not be attached
         */
        public BrightnessSlider create(Context context, @Nullable ViewGroup viewRoot) {
            int layout = getLayout();
            BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                    .inflate(layout, viewRoot, false);
            return new BrightnessSlider(root, mFalsingManager);
        }

        /** Get the layout to inflate based on what slider to use */
        private int getLayout() {
            return R.layout.quick_settings_brightness_dialog;
        }
    }
BrightnessSlider
 类的Factory类,来构造BrightnessController

     /** Factory for creating a {@link BrightnessController}. */
    public static class Factory {
        private final Context mContext;
        private final BroadcastDispatcher mBroadcastDispatcher;

        @Inject
        public Factory(Context context, BroadcastDispatcher broadcastDispatcher) {
            mContext = context;
            mBroadcastDispatcher = broadcastDispatcher;
        }

        /** Create a {@link BrightnessController} */
        public BrightnessController create(ToggleSlider toggleSlider) {
            return new BrightnessController(mContext, toggleSlider, mBroadcastDispatcher);
        }
    }

重点关注构造方法中传递了一个ToggleSlider参数,如果传递ToggleSlider 实现类,那么就实现了控制器和view 的绑定。
控制器中可以通过传递过来的view 来控制view 的各种状态和设置内容。 追踪一下这块创建和添加view 地方:
BrightnessController-> createcreate(ToggleSlider toggleSlider)

QSPanelController

调用地方:QSPanelController 构造方法中:*

        mBrightnessSliderFactory = brightnessSliderFactory;
        //通过factory里的create方法生成
        mBrightnessSlider = mBrightnessSliderFactory.create(getContext(), mView);
        //mView就是上边的QSPanel,可以看到,亮度条是动态添加到容器里的
        mView.setBrightnessView(mBrightnessSlider.getRootView());
        //通过factory里的create方法生成
        mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
创建mBrightnessSlider 变量的创建:
mBrightnessSlider = mBrightnessSliderFactory.create(getContext(), mView);
	-> BrightnessSlider 的create 方法
	 /**
         * Creates the view hierarchy and controller
         *
         * @param context a {@link Context} to inflate the hierarchy
         * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
         *                 hierarchy will not be attached
         */
        public BrightnessSlider create(Context context, @Nullable ViewGroup viewRoot) {
            int layout = getLayout();
            BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                    .inflate(layout, viewRoot, false);
            return new BrightnessSlider(root, mFalsingManager);
        }

return 返回的不就是创建的它的视线的子类吗?BrightnessSliderView
这样就间接实现了 对外Brightness 控制器BrightnessController 和 view 对应BrightnessSliderView 的绑定。

setBrightnessView
关注另外一个点:
	mView.setBrightnessView(mBrightnessSlider.getRootView());
	QSPanelController 里面的mView 肯定是QSPanel, setBrightnessView 又是做什么的呢? 且看代码
	
	 /**
     * Add brightness view above the tile layout.
     *
     * Used to add the brightness slider after construction.
     */
    public void setBrightnessView(@NonNull View view) {
        if (mBrightnessView != null) {
            removeView(mBrightnessView);
            mMovableContentStartIndex--;
        }
        addView(view, 0);
        mBrightnessView = view;
        setBrightnessViewMargin();
        mMovableContentStartIndex++;
    }

就是在QS面板里面添加View,这个地方就是QS 、QQS 相关的核心思想,根布局下面的子布局,都是通过add 添加进去的。

实现方案

实现方案和思想,完全参考亮度进度条的实现方案,故必须对SystemUI有所了解,且对亮度条控制亮度流程、思想比较熟悉,照抄照搬!
几个类的定义,实现volume 控制条,创建几个volume 调节相关的类,

    com/android/systemui/settings/volume/	  
	VolumeController.java 
	VolumeSlider.java
	VolumeSliderView.java
	和对应的布局文件quick_settings_volume_dialog

布局

quick_settings_volume_dialog 完整版本如下:其中SeekBar 完全延用brightness中的ToggleSeekBar
 <com.android.systemui.settings.volume.VolumeSliderView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/brightness_slider"
        android:layout_width="match_parent"
        android:layout_height="@dimen/brightness_mirror_height"
        android:layout_gravity="center"
        android:contentDescription="@string/accessibility_brightness"
        android:importantForAccessibility="no" >

        <com.android.systemui.settings.brightness.ToggleSeekBar
            android:id="@+id/slider"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:minHeight="48dp"
            android:thumb="@null"
            android:background="@null"
            android:paddingStart="0dp"
            android:paddingEnd="0dp"
            android:progressDrawable="@drawable/volume_progress_drawable"
            android:splitTrack="false"
        />
    </com.android.systemui.settings.volume.VolumeSliderView>

构造控制器并绑定View

        QSPanelController 类中,构建View并构建管理view控制器 Control,同时绑定View

        mVolumeSliderFactory = volumeSliderFactory;
        mVolumeSlider = mVolumeSliderFactory.create(getContext(), mView);
        mView.setVolumeView(mVolumeSlider.getRootView());
        mVolumeController = volumeControllerFactory.create(mVolumeSlider);

业务

StatusBar

构造方法中,构建 VolumeSlider.Factory mVolumeSliderFactory,通过注解构造方法实现,其实就是加载View,布局就加载出来了

StatusBarPhoneModule

StatusBarPhoneModule.java 中,生成的StatusBar的, 也是在这里,加载传递VolumeSlider 给StatusBar 的,具体实现方式完全通过Dragger注解来实现。

  /**
     * Provides our instance of StatusBar which is considered optional.
     */
    @Provides
    @SysUISingleton
    static StatusBar provideStatusBar(
	....
	VolumeSlider.Factory volumeSliderFactory,
	....
	){
	  return new StatusBar(
	    ...
		volumeSliderFactory,
		...
	  );
	};

QSPanel QSPanelController

QSPanel QSPanelController 去掉相关的BrightnessMirrorController[镜像控制条,需求一种已经去掉这个类 或者 暂时无用就屏蔽掉]
同时分析,亮度条和进度条是怎么添加到了QSPanel 中去的

  • QSPanel 中添加brightnessSeekView 和 volumSeekView 并设置margin
 public void setVolumeView(@NonNull View view) {
		Log.d(TAG,"setVolumeView");
        if (mVolumeView != null) {
            removeView(mVolumeView);
            mMovableContentStartIndex--;
        }
        addView(view, 1);
        mVolumeView = view;
        setVolumeViewMargin();
        mMovableContentStartIndex++;
		//view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
		
    }
    private void setVolumeViewMargin() {
        if (mVolumeView != null) {
            MarginLayoutParams lp = (MarginLayoutParams) mVolumeView.getLayoutParams();
            lp.topMargin = mContext.getResources()
                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_top);
            lp.bottomMargin = mContext.getResources()
                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_bottom);
            mVolumeView.setLayoutParams(lp);
        }
    }
  • QSPanelController
    QSPanelController 类中,构建View并构建管理view控制器 Control
        mVolumeSliderFactory = volumeSliderFactory;
        mVolumeSlider = mVolumeSliderFactory.create(getContext(), mView);
        mView.setVolumeView(mVolumeSlider.getRootView());
        mVolumeController = volumeControllerFactory.create(mVolumeSlider);  

其它相关类

    com.android.systemui.util  目录下几个类:
 	SoundUtils.kt                   :自定义音量控制工具,将15级别音量调整为100级别,方便展示更加细腻化 
    ContextProvider.java  ApplicationContextProvider.kt           :和ApplicationContextProvider 一起,提供全局的Context支持,在AndroidManifest.xml中配置    
 <!-- Add a Provider to get Application in Project. -->
        <provider
            android:name="com.android.systemui.util.ApplicationContextProvider"
            android:authorities="com.android.systemui.contextprovider"
            android:exported="false"
            android:grantUriPermissions="true">
   </provider>	

资源

RK Android12 SystemUI SystemUI源码方便调试
资源实现源码待RK平台集成后,晚些释放。

Android 12SystemUI下拉框中的亮度的显示隐藏流程如下: 1. 下拉通知时,SystemUI会发送一个通知展开的广播,应用可以通过监听该广播来响应下拉通知的事件。 2. 在通知展开的回调方法中,应用可以根据当前的亮度调节状态来判断是否需要显示亮度。 3. 如果需要显示亮度,则可以获取亮度的控件引用,并设置其可见性为可见。如果不需要显示亮度,则将其可见性设置为不可见。 4. 当通知被收起时,SystemUI会发送一个通知收起的广播,应用可以通过监听该广播来响应通知收起的事件。 5. 在通知收起的回调方法中,应用可以将亮度的可见性设置为不可见。 具体实现步骤可以参考以下代码示例: ``` public class MyNotificationListener extends NotificationListenerService { private SeekBar mBrightnessSeekBar; @Override public void onNotificationPosted(StatusBarNotification sbn) { // 检测到通知展开事件 if (sbn.getNotification().fullScreenIntent != null) { // 获取亮度调节状态 boolean isAdjustBrightness = isAdjustBrightness(sbn); // 如果需要显示亮度 if (isAdjustBrightness) { // 获取亮度控件的引用 mBrightnessSeekBar = findViewById(R.id.brightness_seekbar); // 设置亮度的可见性为可见 mBrightnessSeekBar.setVisibility(View.VISIBLE); } } } @Override public void onNotificationRemoved(StatusBarNotification sbn) { // 检测到通知收起事件 if (sbn.getNotification().fullScreenIntent != null) { // 如果亮度控件存在,将其可见性设置为不可见 if (mBrightnessSeekBar != null) { mBrightnessSeekBar.setVisibility(View.GONE); } } } // 判断是否需要显示亮度 private boolean isAdjustBrightness(StatusBarNotification sbn) { Bundle extras = sbn.getNotification().extras; int flags = extras.getInt(Notification.EXTRA_FLAGS); return (flags & Notification.FLAG_AUTO_BRIGHTNESS) == 0 && (flags & Notification.FLAG_HIGH_PRIORITY) != 0; } } ``` 需要注意的是,具体实现可能会根据不同的设备厂商或Android版本而有所不同,需要根据具体情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ItJavawfc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值