Android 12 SystemUI下拉状态栏禁止QuickQSPanel展开

1.概述

遇到需求,QuickQSPanel首次下拉后展示快捷功能模块以后就是显示QuickQSPanel,而不展开QSPanel,接下来要从下滑手势下拉出状态栏分析功能实现。也就是直接是展开状态。

2、涉及核心类

frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone

复制代码
Android12或者Android13

frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\NotificationPanelViewController.java
frameworks/base/packages/SystemUI/res/layout/status_bar_expanded.xml


Android10
 
frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\NotificationPanelViewController.java
frameworks/base/packages/SystemUI/res/layout/status_bar_expanded.xml

3、SystemUI下拉状态栏禁止QuickQSPanel展开的核心功能分析和实现

在系统SystemUI中,它主要负责反馈系统及应用状态并与用户保持大量的交互,systemui中主要核心布局控件为以下部分

在SystemUI中,QSPanel 创建是从 StatusBar#makeStatusBarView 开始的

接下来分析的核心功能和布局如下:

StatusBar:通知消息提示和状态展现

NavigationBar:返回,HOME,Recent

KeyGuard:锁屏模块可以看做单独的应用,提供基本的手机个人隐私保护

Recents:近期应用管理,以堆叠栈的形式展现。

Notification Panel:展示系统或应用通知内容。提供快速系统设置开关。

VolumeUI:来用展示或控制音量的变化:媒体音量、铃声音量与闹钟音量

截屏界面:长按电源键+音量下键后截屏,用以展示截取的屏幕照片/内容

PowerUI:主要处理和Power相关的事件,比如省电模式切换、电池电量变化和开关屏事件等。

RingtonePlayer:铃声播放

StackDivider:控制管理分屏

PipUI:提供对于画中画模式的管理

3.1 status_bar_expanded.xml下拉状态栏布局分析

复制代码
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->


<com.android.systemui.statusbar.phone.NotificationPanelView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/notification_panel"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">
    <FrameLayout
        android:id="@+id/big_clock_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <ViewStub
        android:id="@+id/keyguard_qs_user_switch_stub"
        android:layout="@layout/keyguard_qs_user_switch"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />

    <include
        layout="@layout/keyguard_bottom_area"
        android:visibility="gone" />

    <ViewStub
        android:id="@+id/keyguard_user_switcher_stub"
        android:layout="@layout/keyguard_user_switcher"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />

    <include layout="@layout/status_bar_expanded_plugin_frame"/>

    <include layout="@layout/dock_info_bottom_area_overlay" />

    <com.android.keyguard.LockIconView
        android:id="@+id/lock_icon_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <!-- Background protection -->
        <ImageView
            android:id="@+id/lock_icon_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/fingerprint_bg"
            android:visibility="invisible"/>

        <ImageView
            android:id="@+id/lock_icon"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/lock_icon_padding"
            android:layout_gravity="center"
            android:scaleType="centerCrop"/>
    </com.android.keyguard.LockIconView>

    <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="@integer/notification_panel_layout_gravity"
        android:id="@+id/notification_container_parent"
        android:clipToPadding="false"
        android:clipChildren="false">

        <include
            layout="@layout/keyguard_status_view"
            android:visibility="gone"/>

        <include layout="@layout/dock_info_overlay" />

        <FrameLayout
            android:id="@+id/qs_frame"
            android:layout="@layout/qs_panel"
            android:layout_width="@dimen/qs_panel_width"
            android:layout_height="0dp"
            android:clipToPadding="false"
            android:clipChildren="false"
            systemui:viewType="com.android.systemui.plugins.qs.QS"
            systemui:layout_constraintStart_toStartOf="parent"
            systemui:layout_constraintEnd_toEndOf="parent"
            systemui:layout_constraintTop_toTopOf="parent"
            systemui:layout_constraintBottom_toBottomOf="parent"
        />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/qs_edge_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            systemui:layout_constraintGuide_percent="0.5"
            android:orientation="vertical"/>

        <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
            android:id="@+id/notification_stack_scroller"
            android:layout_marginTop="@dimen/notification_panel_margin_top"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_marginBottom="@dimen/close_handle_underlap"
            systemui:layout_constraintStart_toStartOf="parent"
            systemui:layout_constraintEnd_toEndOf="parent"
        />

        <include layout="@layout/ambient_indication"
            android:id="@+id/ambient_indication_container" />

        <include layout="@layout/photo_preview_overlay" />

        <include
            layout="@layout/keyguard_status_bar"
            android:visibility="invisible" />

        <Button
            android:id="@+id/report_rejected_touch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
            android:text="@string/report_rejected_touch"
            android:visibility="gone" />
        <com.android.systemui.statusbar.phone.TapAgainView
            android:id="@+id/shade_falsing_tap_again"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            systemui:layout_constraintLeft_toLeftOf="parent"
            systemui:layout_constraintRight_toRightOf="parent"
            systemui:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginBottom="20dp"
            android:paddingHorizontal="16dp"
            android:minHeight="44dp"
            android:elevation="4dp"
            android:background="@drawable/rounded_bg_full"
            android:gravity="center"
            android:text="@string/tap_again"
            android:visibility="gone"
        />
    </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>

    <FrameLayout
        android:id="@+id/preview_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>
</com.android.systemui.statusbar.phone.NotificationPanelView>

在实现SystemUI下拉状态栏禁止QuickQSPanel展开的核心功能中,通过上述的分析得知,

从SystemUI中状态栏下拉的布局status_bar_expanded.xml的布局文件可以看到主要的下拉事件

是在NotificationPanelViewController.java中负责的,所以具体的下滑事件是在NotificationPanelViewController.java中处理的

所以接下来分析NotificationPanelViewController.java有关下拉状态栏的下滑展开事件

3.2 NotificationPanelViewController.java的下拉状态栏下滑事件分析

NotificationPanelViewController是一个以面板形式展示通知内容的主要视图控制器。它可以显示系统通知,

例如来自操作系统的警告、提醒或更新通知,以及来自其他应用程序的通知,如社交媒体应用或

即时消息应用发送的消息通知。通过notificationPanelViewController,用户可以方便地查看和管理收到的通知。

复制代码
    public class NotificationPanelViewController extends PanelViewController { {
      @Override
          protected TouchHandler createTouchHandler() {
              return new TouchHandler() {
                  @Override
                  public boolean onInterceptTouchEvent(MotionEvent event) {
                      if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
                          return false;
                      }
                      initDownStates(event);
                      // Do not let touches go to shade or QS if the bouncer is visible,
                      // but still let user swipe down to expand the panel, dismissing the bouncer.
                      if (mStatusBar.isBouncerShowing()) {
                          return true;
                      }
                      if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
                          mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                          mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
                          return true;
                      }
                      if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
                              && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
                          return true;
                      }
      
                      if (!isFullyCollapsed() && onQsIntercept(event)) {
                          return true;
                      }
                      return super.onInterceptTouchEvent(event);
                  }

在实现SystemUI下拉状态栏禁止QuickQSPanel展开的核心功能中,通过上述的分析得知,

在NotificationPanelViewController中的onInterceptTouchEvent(MotionEvent event) 手势下滑上滑事件的

处理,而具体处理下拉状态栏下滑事件的是在onQsIntercept(MotionEvent event)中继续处理的

接下来看下onQsIntercept(MotionEvent event)处理手势事件

复制代码
 private boolean onQsIntercept(MotionEvent event) {
                int pointerIndex = event.findPointerIndex(mTrackingPointer);
                if (pointerIndex < 0) {
                    pointerIndex = 0;
                    mTrackingPointer = event.getPointerId(pointerIndex);
                }
                final float x = event.getX(pointerIndex);
                final float y = event.getY(pointerIndex);
         
                switch (event.getActionMasked()) {
                    case MotionEvent.ACTION_DOWN:
                        mIntercepting = true;
                        mInitialTouchY = y;
                        mInitialTouchX = x;
                        initVelocityTracker();
                        trackMovement(event);
                        if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        if (mQsExpansionAnimator != null) {
                            onQsExpansionStarted();
                            mInitialHeightOnTouch = mQsExpansionHeight;
                            mQsTracking = true;
                            mIntercepting = false;
                            mNotificationStackScroller.cancelLongPress();
                        }
                        break;
                    case MotionEvent.ACTION_POINTER_UP:
                        final int upPointer = event.getPointerId(event.getActionIndex());
                        if (mTrackingPointer == upPointer) {
                            // gesture is ongoing, find a new pointer to track
                            final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
                            mTrackingPointer = event.getPointerId(newIndex);
                            mInitialTouchX = event.getX(newIndex);
                            mInitialTouchY = event.getY(newIndex);
                        }
                        break;
         
                    case MotionEvent.ACTION_MOVE:
                        final float h = y - mInitialTouchY;
                        trackMovement(event);
                        if (mQsTracking) {
         
                            // Already tracking because onOverscrolled was called. We need to update here
                            // so we don't stop for a frame until the next touch event gets handled in
                            // onTouchEvent.
                            setQsExpansion(h + mInitialHeightOnTouch);
                            trackMovement(event);
                            mIntercepting = false;
                            return true;
                        }
                        if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
                                && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
                            mQsTracking = true;
                            onQsExpansionStarted();
                            notifyExpandingFinished();
                            mInitialHeightOnTouch = mQsExpansionHeight;
                            mInitialTouchY = y;
                            mInitialTouchX = x;
                            mIntercepting = false;
                            mNotificationStackScroller.cancelLongPress();
                            return true;
                        }
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        trackMovement(event);
                        if (mQsTracking) {
                            flingQsWithCurrentVelocity(y,
                                    event.getActionMasked() == MotionEvent.ACTION_CANCEL);
                            mQsTracking = false;
                        }
                        mIntercepting = false;
                        break;
                }
                return false;
            }

在实现SystemUI下拉状态栏禁止QuickQSPanel展开的核心功能中,通过上述的分析得知,

在NotificationPanelViewController中onQsIntercept(MotionEvent event)的是具体处理下拉事件的方法

在case MotionEvent.ACTION_MOVE:中可以根据手势滑动的距离来设置下拉状态栏中的

QSPanel展开和收缩,通过setQsExpansion(h + mInitialHeightOnTouch);来根据手势滑动的

距离来设置QSPanel是否需要展开,所以具体看下setQsExpansion的相关代码

复制代码
 private void setQsExpansion(float height) {
             // add core start
            if (mQs != null) return;
            //add core end
         
                height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
                mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
                if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
                    setQsExpanded(true);
                } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
                    setQsExpanded(false);
                }
                mQsExpansionHeight = height;
                updateQsExpansion();
                requestScrollerTopPaddingUpdate(false /* animate */);
                updateHeaderKeyguardAlpha();
                if (mBarState == StatusBarState.SHADE_LOCKED
                        || mBarState == StatusBarState.KEYGUARD) {
                    updateKeyguardBottomAreaAlpha();
                    updateBigClockAlpha();
                }
                if (mBarState == StatusBarState.SHADE && mQsExpanded
                        && !mStackScrollerOverscrolling && mQsScrimEnabled) {
                    mQsNavbarScrim.setAlpha(getQsExpansionFraction());
                }
         
                if (mAccessibilityManager.isEnabled()) {
                    setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
                }
         
                if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
                        && mFalsingManager.shouldEnforceBouncer()) {
                    mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
                            false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
                }
                if (mExpansionListener != null) {
                    mExpansionListener.onQsExpansionChanged(mQsMaxExpansionHeight != 0
                            ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
                }
                if (DEBUG) {
                    invalidate();
                }
            }

4、修改

在实现SystemUI下拉状态栏禁止QuickQSPanel展开的核心功能中,通过上述的分析得知,

在NotificationPanelViewController.java类中setQsExpansion(float height) 方法来设置下拉状态栏

QSPanel高度的方法中,根据高度判断是否需要展开收缩QSPanel,所以通过增加判断

复制代码
if (mQs != null)
  return;

来去掉下滑展开QSPanel,就可以做到只显示QuickQSPanel,而

不用展开QSPanel,实现单次下拉状态栏,定制UI 就可以在QuickQSPanel中定制就可以了

复制代码
 private void setQsExpansion(float height) {

            // mQs  不等于空直接复用
            if (mQs != null) return;
            // mQs 等于空新建

        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
        mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
                && !mDozing) {
            setQsExpanded(true);
        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
            setQsExpanded(false);
        }
        mQsExpansionHeight = height;
        updateQsExpansion();
        requestScrollerTopPaddingUpdate(false /* animate */);
        updateHeaderKeyguardAlpha();
        if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
            updateKeyguardBottomAreaAlpha();
            positionClockAndNotifications();
            updateBigClockAlpha();
        }

        if (mAccessibilityManager.isEnabled()) {
            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
        }

        if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
                && mFalsingCollector.shouldEnforceBouncer()) {
            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
        }
        for (int i = 0; i < mExpansionListeners.size(); i++) {
            mExpansionListeners.get(i).onQsExpansionChanged(
                    mQsMaxExpansionHeight != 0 ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
        }
        if (DEBUG) {
            mView.invalidate();
        }
    }
相关推荐
爱笑鱼1 小时前
Binder(八):远端进程死了,BinderProxy 为什么还能收到通知?
android
Android-Flutter2 小时前
Kotlin 冷流与热流详解
android·kotlin
zhangphil2 小时前
Android ContentProvider/ContentResolver读图片,跨进程 IPC慢
android
yueqc12 小时前
Android .so 文件压缩
android·so·包体积
龚礼鹏3 小时前
深入解析 Android Automotive (AAOS) 启动流程与 CarService 核心机制(基于 Android 16 最新源码视角)
android
rosmis3 小时前
agent各指标定义
android·java·开发语言
风样滴男人哟4 小时前
PHP特性之反射类ReflectionClass机制
android·开发语言·php
小孔龙5 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
qizayaoshuap5 小时前
鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
android·华为·harmonyos
朝星6 小时前
Framework筑基之Handler消息机制5(应用层)
android