A ---------->SystemUI布局分析

SystemUI 是 Android 操作系统中的一个系统应用程序,它提供了系统用户界面的核心部分,包括状态栏、通知栏、快速设置面板、锁屏界面等(导航栏)。

状态栏通常位于屏幕顶部,显示时间、电池电量、信号强度、Wi-Fi 连接状态等信息。

通知栏是状态栏的一部分,可以下拉以显示更多的应用通知和快速设置选项。

快速设置面板通常通过下拉通知栏来访问,它提供了一系列的快捷开关和设置选项,允许用户快速调整设备的设置,如Wi-Fi、蓝牙、飞行模式、亮度调节、声音设置等。该面板的设计旨在提供快速访问常用设置,而无需打开完整的设置应用。

锁屏页面是设备在一段时间不活动后进入的一种状态,它显示时间和日期,并可能显示未读通知的摘要。

1、锁屏页面

status_bar_expanded.xml
<com.android.systemui.shade.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">

    <com.android.systemui.common.ui.view.LongPressHandlingView
        android:id="@+id/keyguard_long_press"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <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/status_bar_expanded_plugin_frame"/>

    <com.android.systemui.shade.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="0dp"
            android:layout_height="0dp"
            android:clipToPadding="false"
            android:clipChildren="false"
            android:layout_marginHorizontal="@dimen/notification_panel_margin_horizontal"
            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"
        />

        <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
             it a chance to capture clicks before the NonInterceptingScrollView disallows all
             intercepts -->
        <ViewStub
            android:id="@+id/qs_header_stub"
            android:layout_height="wrap_content"
            android:layout_width="match_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"/>

        <!-- This layout should always include a version of
             NotificationStackScrollLayout, as it is expected from
             NotificationPanelViewController. -->
        <include layout="@layout/notification_stack_scroll_layout" />

        <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/m_tap_again"
            android:visibility="gone"
        />
    </com.android.systemui.shade.NotificationsQuickSettingsContainer>

    <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/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">
        <!-- Background protection -->
        <ImageView
            android:id="@+id/lock_icon_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/fingerprint_bg"
            android:visibility="invisible"/>

        <ImageView
            android:id="@+id/lock_icon"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:scaleType="centerCrop"/>

    </com.android.keyguard.LockIconView>

    <include
        layout="@layout/keyguard_bottom_area"
        android:visibility="gone" />
</com.android.systemui.shade.NotificationPanelView>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

keyguard_status_bar.xml
<!-- Extends RelativeLayout -->
<com.android.systemui.statusbar.phone.KeyguardStatusBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/keyguard_header"
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_header_height_keyguard"
    android:baselineAligned="false"
    android:gravity="center_vertical"
    >

    <LinearLayout
        android:id="@+id/status_icon_area"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:layout_alignParentEnd="true"
        android:gravity="center_vertical|end" >

        <include
            android:id="@+id/user_switcher_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
            layout="@layout/status_bar_user_chip_container" />

        <FrameLayout android:id="@+id/system_icons_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginEnd="@dimen/status_bar_padding_end"
            android:gravity="center_vertical|end">
            <include layout="@layout/system_icons" />
        </FrameLayout>

        <ImageView android:id="@+id/multi_user_avatar"
            android:layout_width="@dimen/multi_user_avatar_keyguard_size"
            android:layout_height="@dimen/multi_user_avatar_keyguard_size"
            android:layout_gravity="center"
            android:scaleType="centerInside"/>
    </LinearLayout>

    <Space
        android:id="@+id/cutout_space_view"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:gravity="center"
        android:visibility="gone" />

    <com.android.keyguard.CarrierText
        android:id="@+id/keyguard_carrier_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:layout_marginStart="@dimen/keyguard_carrier_text_margin"
        android:layout_toStartOf="@id/system_icons_container"
        android:gravity="center_vertical"
        android:ellipsize="marquee"
        android:textDirection="locale"
        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
        android:textColor="?attr/wallpaperTextColorSecondary"
        android:singleLine="true"
        systemui:showMissingSim="true"
        systemui:showAirplaneMode="true"
        systemui:debugLocation="Keyguard" />

</com.android.systemui.statusbar.phone.KeyguardStatusBarView>

在这里插入图片描述

keyguard_pattern_view.xml
<!-- This is the screen that shows the 9 circle unlock widget and instructs
     the user how to unlock their device, or make an emergency call.  This
     is the portrait layout.  -->
<com.android.keyguard.KeyguardPatternView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
    android:id="@+id/keyguard_pattern_view"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
    android:layout_gravity="center_horizontal|bottom"
    android:clipChildren="false"
    android:clipToPadding="false">
    <include layout="@layout/keyguard_bouncer_message_area"/>

    <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
        android:id="@+id/bouncer_message_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/pattern_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_weight="1"
        android:layoutDirection="ltr">
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/pattern_top_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            androidprv:layout_constraintGuide_percent="0"
            android:orientation="horizontal" />

        <com.android.internal.widget.LockPatternView
            android:id="@+id/lockPatternView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
            androidprv:layout_constraintBottom_toBottomOf="parent"
            androidprv:layout_constraintLeft_toLeftOf="parent"
            androidprv:layout_constraintRight_toRightOf="parent"
            androidprv:layout_constraintDimensionRatio="1.0"
            androidprv:layout_constraintVertical_bias="1.0"
            />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <include layout="@layout/keyguard_eca"
        android:id="@+id/keyguard_selector_fade_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginTop="@dimen/keyguard_eca_top_margin"
        android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
        android:gravity="center_horizontal" />

</com.android.keyguard.KeyguardPatternView>

在这里插入图片描述

2、QSB and 通知区域

status_bar_expanded.xml
<com.android.systemui.shade.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">

    <com.android.systemui.common.ui.view.LongPressHandlingView
        android:id="@+id/keyguard_long_press"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <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/status_bar_expanded_plugin_frame"/>

    <com.android.systemui.shade.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="0dp"
            android:layout_height="0dp"
            android:clipToPadding="false"
            android:clipChildren="false"
            android:layout_marginHorizontal="@dimen/notification_panel_margin_horizontal"
            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"
        />

        <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
             it a chance to capture clicks before the NonInterceptingScrollView disallows all
             intercepts -->
        <ViewStub
            android:id="@+id/qs_header_stub"
            android:layout_height="wrap_content"
            android:layout_width="match_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"/>

        <!-- This layout should always include a version of
             NotificationStackScrollLayout, as it is expected from
             NotificationPanelViewController. -->
        <include layout="@layout/notification_stack_scroll_layout" />

        <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/m_tap_again"
            android:visibility="gone"
        /><!-- luzongrui change for Tap again bug 73547 20240424-->
    </com.android.systemui.shade.NotificationsQuickSettingsContainer>

    <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/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">
        <!-- Background protection -->
        <ImageView
            android:id="@+id/lock_icon_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/fingerprint_bg"
            android:visibility="invisible"/>

        <ImageView
            android:id="@+id/lock_icon"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:scaleType="centerCrop"/>

    </com.android.keyguard.LockIconView>

    <include
        layout="@layout/keyguard_bottom_area"
        android:visibility="gone" />
</com.android.systemui.shade.NotificationPanelView>

在这里插入图片描述

qs_panel.xml
<com.android.systemui.qs.QSContainerImpl xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/quick_settings_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:clipChildren="false">

    <com.android.systemui.qs.NonInterceptingScrollView
        android:id="@+id/expanded_qs_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="@dimen/qs_panel_elevation"
        android:importantForAccessibility="no"
        android:scrollbars="none"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:layout_weight="1">
        <com.android.systemui.qs.QSPanel
            android:id="@+id/quick_settings_panel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:focusable="true"
            android:accessibilityTraversalBefore="@android:id/edit"
            android:clipToPadding="false"
            android:clipChildren="false">

            <include layout="@layout/qs_footer_impl" />
        </com.android.systemui.qs.QSPanel>
    </com.android.systemui.qs.NonInterceptingScrollView>

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

    <include
        layout="@layout/footer_actions"
        android:id="@+id/qs_footer_actions"
        android:layout_height="@dimen/footer_actions_height"
        android:layout_width="match_parent"
        android:layout_gravity="bottom"
        />

    <include
        android:id="@+id/qs_customize"
        layout="@layout/qs_customize_panel"
        android:visibility="gone" />

</com.android.systemui.qs.QSContainerImpl>

在这里插入图片描述

在这里插入图片描述

qs_footer_impl.xml
<!-- Extends FrameLayout -->
<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/qs_footer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="@dimen/qs_footer_margin"
    android:layout_marginEnd="@dimen/qs_footer_margin"
    android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
    android:background="@android:color/transparent"
    android:baselineAligned="false"
    android:clickable="false"
    android:clipChildren="false"
    android:clipToPadding="false">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/qs_footer_height"
            android:layout_gravity="center_vertical">

            <TextView
                android:id="@+id/build"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:paddingEnd="4dp"
                android:layout_weight="1"
                android:clickable="true"
                android:ellipsize="marquee"
                android:focusable="true"
                android:gravity="center_vertical"
                android:singleLine="true"
                android:textAppearance="@style/TextAppearance.QS.Status.Build"
                android:visibility="gone" />

            <com.android.systemui.qs.PageIndicator
                android:id="@+id/footer_page_indicator"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="center_vertical"
                android:visibility="gone" />

            <FrameLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1">
                <com.android.systemui.statusbar.AlphaOptimizedImageView
                    android:id="@android:id/edit"
                    android:layout_width="@dimen/qs_footer_action_button_size"
                    android:layout_height="@dimen/qs_footer_action_button_size"
                    android:layout_gravity="center_vertical|end"
                    android:background="@drawable/qs_footer_edit_circle"
                    android:clickable="true"
                    android:contentDescription="@string/accessibility_quick_settings_edit"
                    android:focusable="true"
                    android:padding="@dimen/qs_footer_icon_padding"
                    android:src="@*android:drawable/ic_mode_edit"
                    android:tint="?android:attr/textColorPrimary" />
            </FrameLayout>

        </LinearLayout>

</com.android.systemui.qs.QSFooterView>

在这里插入图片描述

3、QSB状态栏

combined_qs_header.xml
<!--
keep split_shade_status_bar height constant to avoid requestLayout calls on each
frame when animating QS <-> QQS transition
-->
<com.android.systemui.util.NoRemeasureMotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/split_shade_status_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/qs_header_height"
    android:minHeight="@dimen/large_screen_shade_header_min_height"
    android:clickable="false"
    android:focusable="true"
    android:paddingLeft="@dimen/qs_panel_padding"
    android:paddingRight="@dimen/qs_panel_padding"
    android:visibility="gone"
    android:theme="@style/Theme.SystemUI.QuickSettings.Header"
    app:layoutDescription="@xml/combined_qs_header_scene">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/qqs_header_bottom_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="@dimen/large_screen_shade_header_min_height"
        />

    <androidx.constraintlayout.widget.Guideline
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/begin_guide"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="0dp"/>

    <androidx.constraintlayout.widget.Guideline
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/end_guide"
        android:orientation="vertical"
        app:layout_constraintGuide_end="0dp"
        />

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

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

    <androidx.constraintlayout.widget.Barrier
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/barrier"
        app:barrierDirection="start"
        app:constraint_referenced_ids="statusIcons,privacy_container" />

    <com.android.systemui.statusbar.policy.Clock
        android:id="@+id/clock"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:gravity="start|center_vertical"
        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
        android:singleLine="true"
        android:textDirection="locale"
        android:textAppearance="@style/TextAppearance.QS.Status"
        android:transformPivotX="0dp"
        android:transformPivotY="24dp"
        android:scaleX="1"
        android:scaleY="1"
    />

    <com.android.systemui.statusbar.policy.VariableDateView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_gravity="start|center_vertical"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:textDirection="locale"
        android:textAppearance="@style/TextAppearance.QS.Status"
        app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
        app:shortDatePattern="@string/abbrev_month_day_no_year"
    />

    <include
        android:id="@+id/carrier_group"
        layout="@layout/shade_carrier_group"
        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
        android:minHeight="@dimen/large_screen_shade_header_min_height"
        app:layout_constraintWidth_min="48dp"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constrainedWidth="true"
        android:layout_gravity="end|center_vertical"
        android:layout_marginStart="8dp"
        app:layout_constraintStart_toEndOf="@id/date"
        app:layout_constraintEnd_toStartOf="@id/statusIcons"
        app:layout_constraintTop_toTopOf="@id/clock"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="1"
    />

    <com.android.systemui.statusbar.phone.StatusIconContainer
        android:id="@+id/statusIcons"
        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
        android:paddingEnd="@dimen/signal_cluster_battery_padding"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/large_screen_shade_header_min_height"
        app:layout_constraintStart_toEndOf="@id/carrier_group"
        app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
        app:layout_constraintTop_toTopOf="@id/clock"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="1"
    />

    <com.android.systemui.battery.BatteryMeterView
        android:id="@+id/batteryRemainingIcon"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/large_screen_shade_header_min_height"
        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
        app:layout_constrainedWidth="true"
        app:textAppearance="@style/TextAppearance.QS.Status"
        app:layout_constraintStart_toEndOf="@id/statusIcons"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/clock"
        app:layout_constraintBottom_toBottomOf="parent"
    />

    <FrameLayout
        android:id="@+id/privacy_container"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/large_screen_shade_header_min_height"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="@id/date"
        app:layout_constraintEnd_toEndOf="@id/end_guide"
        app:layout_constraintTop_toTopOf="@id/date">

        <com.android.systemui.privacy.OngoingPrivacyChip
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />

    </FrameLayout>

</com.android.systemui.util.NoRemeasureMotionLayout>

在这里插入图片描述

4、桌面状态栏

status_bar.xml
<!--    android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.phone.PhoneStatusBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_height"
    android:id="@+id/status_bar"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    android:accessibilityPaneTitle="@string/status_bar"
    >

    <ImageView
        android:id="@+id/notification_lights_out"
        android:layout_width="@dimen/status_bar_icon_size"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingBottom="2dip"
        android:src="@drawable/ic_sysbar_lights_out_dot_small"
        android:scaleType="center"
        android:visibility="gone"
        />

    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingEnd="@dimen/status_bar_padding_end"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:orientation="horizontal">

        <!-- Container for the entire start half of the status bar. It will always use the same
             width, independent of the number of visible children and sub-children. -->
        <FrameLayout
            android:id="@+id/status_bar_start_side_container"
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:clipChildren="false"
            android:layout_weight="1">

            <!-- Container that is wrapped around the views on the start half of the status bar.
                 Its width will change with the number of visible children and sub-children.
                 It is useful when we want to know the visible bounds of the content. -->
            <FrameLayout
                android:id="@+id/status_bar_start_side_content"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clipChildren="false">

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

                <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
                     individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
                     and DISABLE_NOTIFICATION_ICONS, respectively -->
                <LinearLayout
                    android:id="@+id/status_bar_start_side_except_heads_up"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:clipChildren="false">
                    <ViewStub
                        android:id="@+id/operator_name"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout="@layout/operator_name" />

                    <com.android.systemui.statusbar.policy.Clock
                        android:id="@+id/clock"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                        android:singleLine="true"
                        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
                        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
                        android:gravity="center_vertical|start"
                    />

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

                    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                        android:id="@+id/notification_icon_area"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:orientation="horizontal"
                        android:clipChildren="false"/>

                </LinearLayout>
            </FrameLayout>
        </FrameLayout>

        <!-- Space should cover the notch (if it exists) and let other views lay out around it -->
        <android.widget.Space
            android:id="@+id/cutout_space_view"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:gravity="center_horizontal|center_vertical"
        />

        <!-- Container for the entire end half of the status bar. It will always use the same
             width, independent of the number of visible children and sub-children. -->
        <FrameLayout
            android:id="@+id/status_bar_end_side_container"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clipChildren="false">

            <!-- Container that is wrapped around the views on the end half of the
                 status bar. Its width will change with the number of visible children and
                 sub-children.
                 It is useful when we want know the visible bounds of the content.-->
            <com.android.keyguard.AlphaOptimizedLinearLayout
                android:id="@+id/status_bar_end_side_content"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="end"
                android:orientation="horizontal"
                android:gravity="center_vertical|end"
                android:clipChildren="false">

                <include
                    android:id="@+id/user_switcher_container"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
                    layout="@layout/status_bar_user_chip_container" />

                <include layout="@layout/system_icons" />
            </com.android.keyguard.AlphaOptimizedLinearLayout>
        </FrameLayout>
    </LinearLayout>

</com.android.systemui.statusbar.phone.PhoneStatusBarView>

在这里插入图片描述

5、QSB编辑页面

status_bar_expanded.xml

在这里插入图片描述

qs_panel.xml
<com.android.systemui.qs.QSContainerImpl xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/quick_settings_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:clipChildren="false">

    <com.android.systemui.qs.NonInterceptingScrollView
        android:id="@+id/expanded_qs_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="@dimen/qs_panel_elevation"
        android:importantForAccessibility="no"
        android:scrollbars="none"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:layout_weight="1">
        <com.android.systemui.qs.QSPanel
            android:id="@+id/quick_settings_panel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:focusable="true"
            android:accessibilityTraversalBefore="@android:id/edit"
            android:clipToPadding="false"
            android:clipChildren="false">

            <include layout="@layout/qs_footer_impl" />
        </com.android.systemui.qs.QSPanel>
    </com.android.systemui.qs.NonInterceptingScrollView>

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

    <include
        layout="@layout/footer_actions"
        android:id="@+id/qs_footer_actions"
        android:layout_height="@dimen/footer_actions_height"
        android:layout_width="match_parent"
        android:layout_gravity="bottom"
        />

    <include
        android:id="@+id/qs_customize"
        layout="@layout/qs_customize_panel"
        android:visibility="gone" />

</com.android.systemui.qs.QSContainerImpl>

qs_customize_panel.xml
<!-- Height is 0 because it will be managed by the QS manually -->
<com.android.systemui.qs.customize.QSCustomizer
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:elevation="4dp"
    android:orientation="vertical"
    android:gravity="center_horizontal">

</com.android.systemui.qs.customize.QSCustomizer>

QSCustomizer.java
    public QSCustomizer(Context context, AttributeSet attrs) {
        super(context, attrs);

        LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
        mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
        Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
        TypedValue value = new TypedValue();
        mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
        toolbar.setNavigationIcon(
                getResources().getDrawable(value.resourceId, mContext.getTheme()));

        toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        toolbar.setTitle(R.string.qs_edit);
        mRecyclerView = findViewById(android.R.id.list);
        mTransparentView = findViewById(R.id.customizer_transparent_view);
        DefaultItemAnimator animator = new DefaultItemAnimator();
        animator.setMoveDuration(TileAdapter.MOVE_DURATION);
        mRecyclerView.setItemAnimator(animator);

        updateTransparentViewHeight();
    }

在这里插入图片描述

qs_customize_panel_content.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">->
    <View
        android:id="@+id/customizer_transparent_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/qs_header_system_icons_area_height"
        android:background="@android:color/transparent" />

    <com.android.keyguard.AlphaOptimizedLinearLayout
        android:id="@+id/customize_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:paddingStart="@dimen/qs_customize_internal_side_paddings"
        android:paddingEnd="@dimen/qs_customize_internal_side_paddings"
        android:background="@drawable/qs_customizer_background">
        <Toolbar
            android:id="@*android:id/action_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/qs_customizer_toolbar"
            android:navigationContentDescription="@*android:string/action_bar_up_description"
            style="@style/QSCustomizeToolbar"
            />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:paddingStart="@dimen/qs_customize_internal_side_paddings"
            android:paddingEnd="@dimen/qs_customize_internal_side_paddings"
            android:paddingBottom="28dp"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:scrollIndicators="top"
            android:scrollbars="vertical"
            android:scrollbarStyle="outsideOverlay"
            android:importantForAccessibility="auto" />
    </com.android.keyguard.AlphaOptimizedLinearLayout>

    <View
        android:id="@+id/nav_bar_background"
        android:layout_width="match_parent"
        android:layout_height="@dimen/navigation_bar_size"
        android:layout_gravity="bottom"
        android:background="#ff000000" />
</merge>

在这里插入图片描述

B ---------->SystemUI启动流程分析

在这里插入图片描述
BootLoader:启动加载器是设备启动过程的第一阶段,它初始化硬件并加载操作系统内核。

Linux Kernel:内核是操作系统的核心,负责管理系统资源,如内存、处理器和设备驱动程序。

init:init 是系统启动后运行的第一个用户空间进程(进程ID为1)。它负责启动系统上的其他进程和服务。

Zygote:Zygote是Android中的一个特殊进程,它启动后会加载Java核心库,并为其他应用进程提供服务。它通过fork操作来创建新的应用进程,Zygote 进程在启动后,会创建另一个重要的进程SystemServer进程。

SystemServer:系统服务器是Android中一个关键的守护进程,它启动了Android系统的核心服务,如窗口管理器、活动管理器、包管理器等。

ActivityManagerService:活动管理器服务负责管理应用程序的活动(Activity)生命周期,并处理应用程序的启动和切换

简而言之:

设备开机,BootLoader启动。

加载Linux内核。

内核初始化后,启动init进程。

init进程启动Zygote进程。

Zygote进程启动后,会加载核心库并准备创建应用进程。

系统服务器(SystemServer)启动,它将启动一系列系统服务。

SystemUI启动

SystemServer.run()-> startOtherServices()-> startSystemUi()

SystemServer由ZygoteInit进程创建并启动
在这里插入图片描述



于是走到SystemUIService的onCreate()方法

在这里插入图片描述



这样相关类就注入结束了,接着回到 SystemUIApplication 的 startServicesIfNeeded() 方法

在这里插入图片描述



在这里插入图片描述
到此 CoreStartable (SystemUI) 启动流程分析完毕。

C ---------->SystemUI屏幕亮度调节代码分析

上面布局分析没分析这个亮度调节栏,就放这分析吧。

quick_settings_brightness_dialog.xml
    <com.android.systemui.settings.brightness.BrightnessSliderView
        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/brightness_progress_drawable"
            android:splitTrack="false"
        />
    </com.android.systemui.settings.brightness.BrightnessSliderView>

在这里插入图片描述

BrightnessSliderView.java
    // Inflated from quick_settings_brightness_dialog
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        setLayerType(LAYER_TYPE_HARDWARE, null);

        mSlider = requireViewById(R.id.slider);
        mSlider.setAccessibilityLabel(getContentDescription().toString());

        // Finds the progress drawable. Assumes brightness_progress_drawable.xml
        try {
            LayerDrawable progress = (LayerDrawable) mSlider.getProgressDrawable();
            DrawableWrapper progressSlider = (DrawableWrapper) progress
                    .findDrawableByLayerId(android.R.id.progress);
            LayerDrawable actualProgressSlider = (LayerDrawable) progressSlider.getDrawable();
            mProgressDrawable = actualProgressSlider.findDrawableByLayerId(R.id.slider_foreground);
        } catch (Exception e) {
            // Nothing to do, mProgressDrawable will be null.
        }
    }

在这里插入图片描述

BrightnessController.java
    private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
    private volatile boolean mIsVrModeEnabled;
    private boolean mListening;
    private boolean mExternalChange;
    private boolean mControlValueInitialized;
    private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
    private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
mAutomatic:屏幕亮度是否根据环境光传感器自动调整。
                  
mListening:是否正在监听外部事件

mIsVrModeEnabled:是否启用了虚拟现实(VR)模式(不同亮度的设置)。

mExternalChange:追踪亮度变化是否由外部因素引起,而非用户直接通过滑块进行调整。

mControlValueInitialized:记控制值是否已经被初始化。这确保在应用亮度设置之前,相关的值已经被正确设置。

mBrightnessMin:存储亮度的最小值,默认值为PowerManager.BRIGHTNESS_MIN,这是系统定义的亮度最小值常量。

mBrightnessMax:存储亮度的最大值,默认值为PowerManager.BRIGHTNESS_MAX,这是系统定义的亮度最大值常量。

这些变量是控制亮度逻辑的重要组成部分,它们使得BrightnessSliderView类能够根据不同的条件和用户交互来调整屏幕上的亮度。

BrightnessController.java
    private void updateSlider(float brightnessValue, boolean inVrMode) {
        final float min = mBrightnessMin;
        final float max = mBrightnessMax;

        // Ensure the slider is in a fixed position first, then check if we should animate.
        if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
            mSliderAnimator.cancel();
        }
        // convertGammaToLinearFloat returns 0-1
        if (BrightnessSynchronizer.floatEquals(brightnessValue,
                convertGammaToLinearExt(mControl.getValue(), min, max))) {//[CH108Q][62218]change by chenchuanliang for brightness change smooth merge from CH108Q2022060
            // If the value in the slider is equal to the value on the current brightness
            // then the slider does not need to animate, since the brightness will not change.
            return;
        }
        // Returns GAMMA_SPACE_MIN - GAMMA_SPACE_MAX
        //[CH108Q][62218]change by chenchuanliang for brightness change smooth merge from CH108Q20220602
        final int sliderVal = convertLinearToGammaExt(brightnessValue, min, max);
        animateSliderTo(sliderVal);
    }

在这里插入图片描述


亮度最小值由PowerManager.BRIGHTNESS_MIN控制

在这里插入图片描述

D ---------->SystemUI之QS面板分析

1、QS面板构成元素分析

QS面板实际上有多种状态,包括:
1、Quick Quick Settings (QQS) : 即初级展开面板,是一次下拉面板看到的简版QS面板,包含少量的开关,如下左侧的图。
2、Quick Settings (QS) : 完整QS面板,是二次下拉面板看到的完成QS面板,其包含更多的开关,如下右侧的图。
3、另外还有开关编辑面板,开关详情页面。

在这里插入图片描述
注:SystemUI中称通知栏下拉面板开关区域中的单个开关为Tile,下面进行分析QS面板中主要的几大类簇。

1、QSTile类簇
在这里插入图片描述

packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
packages/SystemUI/src/com/android/systemui/qs/tiles/XxxTile.java

该类簇构成了的Tile的"后端",负责处理单个Tile的逻辑处理,其中:
QSTile:接口,主要定义了所有Tile的通用行为,如注册监听、点击事件的处理,Tile视图中Icon元素(QSIconView)的构建,刷新Tile状态等。

QSTileImpl:实现了QSTile 定义的通用行为,同时提供了一系列的抽象接口(详见类图)允许不同类型的子类去做差异化实现。
后续所有的开关都需要继承自QSTileImpl。如BluetoothTile通过继承QSTileImpl来享用其提供的通用方法,不需要每个开关都去做实现,
同时利用差异化接口便可实现开关自身的特有逻辑,如BluetoothTile的handleClick则可以打开蓝牙开关。

2、QSTileView类簇
在这里插入图片描述

packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java

该类簇构成了的Tile的视图层,负责处理单个Tile的界面展示,其中:
QSTileView:抽象类,定义了Tile视图相关的操作,如返回只包含图标的QSIconView、视图刷新onStateChanged(State state)等。
在其构建的视图的基础上,扩展了label相关的东西,label即为开关中的文字描述视图。

QSTileViewImpl:实现了QSTileView中定义的抽象接口,同时在其构造方法中完成了Tile视图的构建,包括背景的处理、点击效果的处理(如ripple)、点击事件的处理等。

3、QSHost类簇
在这里插入图片描述

packages/SystemUI/src/com/android/systemui/qs/QSHost.java
packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSFactory.java
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java

该类簇主要完成单个Tile的构建,其中:
QSHost:接口,主要向外界提供获取QSTile集合的接口(getTiles())以及作为Tile对外沟通的桥梁,例如点击某个开关后需要触发收起面板的操作,开关便会通过QSHost来触发收起面板的操作。

QSTileHost :实现了QSHost中定义的接口,同时扩展了创建Tile后端对象QSTile和创建Tile视图对象QSTileView的接口。创建时使用了工厂模式,由QSFactoryImpl类实现

  其中QSTileHost作为外界创建Tile的入口,会在对象构造的过程中先去创建Tile后端对象QSTile集合,这个集合在后续创建完整Tile对象时会用到。 具体创建哪些Tile则是通过获取配置在 config.xml 中的字段来决定了。具体过程可查看 QSTileHost.onTuningChanged(String key, String newValue) 方法。

config.xml
    <!-- The default tiles to display in QuickSettings -->
    <string name="quick_settings_tiles_default" translatable="false">
        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,screenshot,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService)
    </string><!-- luzongrui add screenshot tile 20240328-->

在这里插入图片描述

4、QSPanel类簇

在这里插入图片描述

packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
packages/SystemUI/src/com/android/systemui/qs/QSTileLayout
packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java

该类簇主要完成QS面板元素的动态添加,其中:
QSPanel:对应我们前面说的QS面板,即二次展开面板,是该页面的顶层容器,其嵌套在一个ScrollView中. 负责动态添加相应的元素,添加的元素包括 [亮度条 / 根据屏幕方向动态添加开关容器 QSTileLayout / Footer ] 等。
同时为这些元素提供了一系列的操作接口,如:
1、为开关容器创建具体开关对象(通过QSHost) 并为其添加刷新监听器,在后续开关后端收到开关状态刷新需求时,将刷新需求分发到对应的开关视图层.
2、开关详情页(Details)的创建与刷新  
3、QS面板展开状态变化时做相应的处理(setExpanded(boolean expanded))

QuickQSPanel:对应我们前面说的初级展开面板QQS面板,继承自QSPanel并对展示的Tile数做了限制(通过setMaxTiles(int)),
同时复写了父类提供的添加子元素的方法,按需添加QQS面板的元素,因为QQS面板是QS面板的精简版,所以很多子元素未做添加。

QSTileLayout:开关容器接口,主要定义了开关容器绘制相关的接口。
TileLayout:QQS面板开关容器类,负责精简QS面板的绘制,继承自 Viewgroup
PagedTileLayout:QS面板开关容器类,负责QS面板的绘制,继承自 ViewPager

5、QS类簇

在这里插入图片描述

packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java

该类簇主要作为整个QS面板的顶层容器,主要处理QS面板的展开/收起逻辑,其中:

QS:接口,主要定义了 QS 面板展开/收起相关的接口。

QSFragment:继承自 Fragment,实现了 QS 接口,主要负责接收 QS 面板展开/收起状态的改变,并将最新状态同步给该 Fragment 中的 View 元素,如 QSContainerImpl。

QSContainerImpl:添加到QSFragment中的自定义 View,继承自FrameLayout对应布局 qs_panel.xml ,主要通过接收来自 QSFragment 的面板展开/收起状态的变化并做刷新。

2、QS面板内部实现梳理

a:QS面板开关集合构建流程

  QsPanel 中除了创建各个开关View,还创建了亮度条,Footer等元素。另外QsFragment / QSHost 等元素是在 SystemUI 启动流程中通过注入或反射构建的,其前期构建流程跳过分析,先看看整体的流程图。
在这里插入图片描述
  简单说就是QSTileHost对象在构建初期就借助QSFactoryImpl工具对象提前创建好了各个开关的后端对象QSTile,而后QSPanel在初始化的过程中,再次利用QSTileHost去构建各个开关的视图对象QSTileView,至此一个完整的开关就构建完成,最后add到开关容器PagedTileLayout中去。

b:Tile后端是如何与Tile视图层联系的

在这里插入图片描述
前面QSTile的类簇中我们可以看到其有多个内部类,与此相关的内部类包括 Callback 和 State。

Tile 视图与后端的联系就是借助这两个内部类以及QSPanel这个中介产生联系的。

前面 在梳理QS面板开关集合构建流程时可以看到步骤10通过addTile函数来构建Tile开关对象,其代码细节如下:

QSPanel.java
    final void addTile(QSPanelControllerBase.TileRecord tileRecord) {
        final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                drawTile(tileRecord, state);
            }
        };

        tileRecord.tile.addCallback(callback);
        tileRecord.callback = callback;
        tileRecord.tileView.init(tileRecord.tile);
        tileRecord.tile.refreshState();

        if (mTileLayout != null) {
            mTileLayout.addTile(tileRecord);
        }
    }

在这里插入图片描述
可以看到QSPanel向 tileRecord.tile 即后端注册了一个回调器,并在回调发生时将开关状态State传递给 tileRecord.tileView, 即开关视图层去做视图刷新。

c: Tile的一次点击事件背后的流程是怎么样的

上面介绍addTile函数时可以看到一句tileRecord.tileView.init(tileRecord.tile);这里完成了将视图层的点击事件转交给后端的操作。

QSTileViewImpl.kt
    override fun init(tile: QSTile) {
        init(
                { v: View? -> tile.click(this) },
                { view: View? ->
                    tile.longClick(this)
                    true
                }
        )
    }

    private fun init(
        click: OnClickListener?,
        longClick: OnLongClickListener?
    ) {
        setOnClickListener(click)
        onLongClickListener = longClick
    }

在这里插入图片描述
即将QSTileView收到的点击事件或长按事件分别交给QSTile对应的点击、长按函数处理。

QSTile.java是一个接口,具体的实现逻辑是在QSTileImpl.java中。

QSTileImpl.java
    public void click(@Nullable View view) {
        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)
                .addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
                getInstanceId());
        final int eventId = mClickEventId++;
        mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                eventId);
        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
            mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget();
        }
    }

    public void secondaryClick(@Nullable View view) {
        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)
                .addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
                getInstanceId());
        final int eventId = mClickEventId++;
        mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
                mState.state, eventId);
        mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget();
    }

    @Override
    public void longClick(@Nullable View view) {
        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
                .addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
                getInstanceId());
        final int eventId = mClickEventId++;
        mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                eventId);
        mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
    }

在这里插入图片描述

QSTileImpl.java
    protected final class H extends Handler {
        private static final int ADD_CALLBACK = 1;
        private static final int CLICK = 2;
        private static final int SECONDARY_CLICK = 3;
        private static final int LONG_CLICK = 4;
        private static final int REFRESH_STATE = 5;
        private static final int USER_SWITCH = 6;
        private static final int DESTROY = 7;
        private static final int REMOVE_CALLBACKS = 8;
        private static final int REMOVE_CALLBACK = 9;
        private static final int SET_LISTENING = 10;
        @VisibleForTesting
        protected static final int STALE = 11;
        private static final int INITIALIZE = 12;

        @VisibleForTesting
        protected H(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            String name = null;
            try {
                if (msg.what == ADD_CALLBACK) {
                    name = "handleAddCallback";
                    handleAddCallback((QSTile.Callback) msg.obj);
                } else if (msg.what == REMOVE_CALLBACKS) {
                    name = "handleRemoveCallbacks";
                    handleRemoveCallbacks();
                } else if (msg.what == REMOVE_CALLBACK) {
                    name = "handleRemoveCallback";
                    handleRemoveCallback((QSTile.Callback) msg.obj);
                } else if (msg.what == CLICK) {
                    name = "handleClick";
                    if (mState.disabledByPolicy) {
                        Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
                                mContext, mEnforcedAdmin);
                        mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
                    } else {
                        mQSLogger.logHandleClick(mTileSpec, msg.arg1);
                        handleClick((View) msg.obj);
                    }
                } else if (msg.what == SECONDARY_CLICK) {
                    name = "handleSecondaryClick";
                    mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
                    handleSecondaryClick((View) msg.obj);
                } else if (msg.what == LONG_CLICK) {
                    name = "handleLongClick";
                    mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
                    handleLongClick((View) msg.obj);
                } else if (msg.what == REFRESH_STATE) {
                    name = "handleRefreshState";
                    handleRefreshState(msg.obj);
                } else if (msg.what == USER_SWITCH) {
                    name = "handleUserSwitch";
                    handleUserSwitch(msg.arg1);
                } else if (msg.what == DESTROY) {
                    name = "handleDestroy";
                    handleDestroy();
                } else if (msg.what == SET_LISTENING) {
                    name = "handleSetListeningInternal";
                    handleSetListeningInternal(msg.obj, msg.arg1 != 0);
                } else if (msg.what == STALE) {
                    name = "handleStale";
                    handleStale();
                } else if (msg.what == INITIALIZE) {
                    name = "initialize";
                    handleInitialize();
                } else {
                    throw new IllegalArgumentException("Unknown msg: " + msg.what);
                }
            } catch (Throwable t) {
                final String error = "Error in " + name;
                Log.w(TAG, error, t);
            }
        }
    }

在这里插入图片描述

    protected final void handleRefreshState(@Nullable Object arg) {
        handleUpdateState(mTmpState, arg);
        boolean changed = mTmpState.copyTo(mState);
        if (mReadyState == READY_STATE_READYING) {
            mReadyState = READY_STATE_READY;
            changed = true;
        }
        if (changed) {
            mQSLogger.logTileUpdated(mTileSpec, mState);
            handleStateChanged();
        }
        mHandler.removeMessages(H.STALE);
        mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout());
        setListening(mStaleListener, false);
    }

    private void handleStateChanged() {
        if (mCallbacks.size() != 0) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onStateChanged(mState);
            }
        }
    }

在这里插入图片描述
在这里插入图片描述

可以看到QSTileImpl用一个State类型的临时变量去handleUpdateState函数中收集当前开关的最新状态,而这个函数是个抽象方法,实现依旧是在各个开关子类中,我们看下Bluetooth开关

BluetoothTile.java
    @Override
    protected void handleUpdateState(BooleanState state, Object arg) {
        checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
        final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
        final boolean enabled = transientEnabling || mController.isBluetoothEnabled();
        final boolean connected = mController.isBluetoothConnected();
        final boolean connecting = mController.isBluetoothConnecting();
        state.isTransient = transientEnabling || connecting ||
                mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
        if (!enabled || !connected || state.isTransient) {
            stopListeningToStaleDeviceMetadata();
        }
        state.dualTarget = true;
        state.value = enabled;
        if (state.slash == null) {
            state.slash = new SlashState();
        }
        state.slash.isSlashed = !enabled;
        state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
        state.secondaryLabel = TextUtils.emptyIfNull(
                getSecondaryLabel(enabled, connecting, connected, state.isTransient));
        state.contentDescription = mContext.getString(
                R.string.accessibility_quick_settings_bluetooth);
        state.stateDescription = "";

        if (enabled) {
            if (connected) {
                state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_on);
                if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
                    state.label = mController.getConnectedDeviceName();
                }
                state.stateDescription =
                        mContext.getString(R.string.accessibility_bluetooth_name, state.label)
                                + ", " + state.secondaryLabel;
            } else if (state.isTransient) {
                state.icon = ResourceIcon.get(
                        R.drawable.qs_bluetooth_icon_search);
                state.stateDescription = state.secondaryLabel;
            } else {
                state.icon =
                        ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
                state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
            }
            state.state = Tile.STATE_ACTIVE;
        } else {
            state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
            state.state = Tile.STATE_INACTIVE;
        }

        state.expandedAccessibilityClassName = Switch.class.getName();
    }

在这里插入图片描述
handleUpdateState函数收集完开关状态后,如果开关状态发生了改变则会导致handleStateChanged() 被调用

QSTileImpl.java
    private void handleStateChanged() {
        if (mCallbacks.size() != 0) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onStateChanged(mState);
            }
        }
    }
QSPanel.java
    final void addTile(QSPanelControllerBase.TileRecord tileRecord) {
        final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                drawTile(tileRecord, state);
            }
        };

        tileRecord.tile.addCallback(callback);
        tileRecord.callback = callback;
        tileRecord.tileView.init(tileRecord.tile);
        tileRecord.tile.refreshState();

        if (mTileLayout != null) {
            mTileLayout.addTile(tileRecord);
        }
    }

在这里插入图片描述
这里就和前面(Tile后端是如何与Tile视图层联系的)关联起来了,即 state会被传递到视图层。

QSTileView.java
    public abstract void onStateChanged(State state);

在这里插入图片描述

QSTileViewImpl.kt
    override fun onStateChanged(state: QSTile.State) {
        // We cannot use the handler here because sometimes, the views are not attached (if they
        // are in a page that the ViewPager hasn't attached). Instead, we use a runnable where
        // all its instances are `equal` to each other, so they can be used to remove them from the
        // queue.
        // This means that at any given time there's at most one enqueued runnable to change state.
        // However, as we only ever care about the last state posted, this is fine.
        val runnable = StateChangeRunnable(state.copy())
        removeCallbacks(runnable)
        post(runnable)
    }

当Tile的状态发生变化时,创建一个任务来处理这个变化。通过 post 方法,状态更新的操作将在适当的时机异步执行。

    inner class StateChangeRunnable(private val state: QSTile.State) : Runnable {
        override fun run() {
            handleStateChanged(state)
        }

        // We want all instances of this runnable to be equal to each other, so they can be used to
        // remove previous instances from the Handler/RunQueue of this view
        override fun equals(other: Any?): Boolean {
            return other is StateChangeRunnable
        }

        // This makes sure that all instances have the same hashcode (because they are `equal`)
        override fun hashCode(): Int {
            return StateChangeRunnable::class.hashCode()
        }
    }

在这里插入图片描述
handleStateChanged函数是真正做开关视图显示刷新的地方

    // HANDLE STATE CHANGES RELATED METHODS

    protected open fun handleStateChanged(state: QSTile.State) {
        val allowAnimations = animationsEnabled()
        showRippleEffect = state.showRippleEffect
        isClickable = state.state != Tile.STATE_UNAVAILABLE
        isLongClickable = state.handlesLongClick
        icon.setIcon(state, allowAnimations)
        contentDescription = state.contentDescription

        // State handling and description
        val stateDescription = StringBuilder()
        val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
        val stateText = state.getStateText(arrayResId, resources)
        state.secondaryLabel = state.getSecondaryLabel(stateText)
        if (!TextUtils.isEmpty(stateText)) {
            stateDescription.append(stateText)
        }
        if (state.disabledByPolicy && state.state != Tile.STATE_UNAVAILABLE) {
            stateDescription.append(", ")
            stateDescription.append(getUnavailableText(state.spec))
        }
        if (!TextUtils.isEmpty(state.stateDescription)) {
            stateDescription.append(", ")
            stateDescription.append(state.stateDescription)
            if (lastState != INVALID && state.state == lastState &&
                    state.stateDescription != lastStateDescription) {
                stateDescriptionDeltas = state.stateDescription
            }
        }

        setStateDescription(stateDescription.toString())
        lastStateDescription = state.stateDescription

        accessibilityClass = if (state.state == Tile.STATE_UNAVAILABLE) {
            null
        } else {
            state.expandedAccessibilityClassName
        }

        if (state is BooleanState) {
            val newState = state.value
            if (tileState != newState) {
                tileState = newState
            }
        }
        //

        // Labels
        if (!Objects.equals(label.text, state.label)) {
            label.text = state.label
        }
        if (!Objects.equals(secondaryLabel.text, state.secondaryLabel)) {
            secondaryLabel.text = state.secondaryLabel
            secondaryLabel.visibility = if (TextUtils.isEmpty(state.secondaryLabel)) {
                GONE
            } else {
                VISIBLE
            }
        }

        // Colors
        if (state.state != lastState || state.disabledByPolicy != lastDisabledByPolicy) {
            singleAnimator.cancel()
            mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
                    state.spec,
                    state.state,
                    state.disabledByPolicy,
                    getBackgroundColorForState(state.state, state.disabledByPolicy))
            if (allowAnimations) {
                singleAnimator.setValues(
                        colorValuesHolder(
                                BACKGROUND_NAME,
                                paintColor,
                                getBackgroundColorForState(state.state, state.disabledByPolicy)
                        ),
                        colorValuesHolder(
                                LABEL_NAME,
                                label.currentTextColor,
                                getLabelColorForState(state.state, state.disabledByPolicy)
                        ),
                        colorValuesHolder(
                                SECONDARY_LABEL_NAME,
                                secondaryLabel.currentTextColor,
                                getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
                        ),
                        colorValuesHolder(
                                CHEVRON_NAME,
                                chevronView.imageTintList?.defaultColor ?: 0,
                                getChevronColorForState(state.state, state.disabledByPolicy)
                        )
                    )
                singleAnimator.start()
            } else {
                setAllColors(
                    getBackgroundColorForState(state.state, state.disabledByPolicy),
                    getLabelColorForState(state.state, state.disabledByPolicy),
                    getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
                    getChevronColorForState(state.state, state.disabledByPolicy)
                )
            }
        }

        // Right side icon
        loadSideViewDrawableIfNecessary(state)

        label.isEnabled = !state.disabledByPolicy

        lastState = state.state
        lastDisabledByPolicy = state.disabledByPolicy
    }

在这里插入图片描述
在这里插入图片描述

至此,就将开关点击背后的流程梳理清楚了

E ---------->SystemUI系统导航栏浅析

NavigationBar在CentralSurfacesImpl中调用start()时进行初始化

CentralSurfacesImpl.java

在这里插入图片描述

    @Override
    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
        makeStatusBarView(result);
        mNotificationShadeWindowController.attach();
        mStatusBarWindowController.attach();
    }

在这里插入图片描述
在这里插入图片描述

    protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
        mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
    }

在这里插入图片描述

NavigationBarController.java
    /**
     * Adds a navigation bar on default display or an external display if the display supports
     * system decorations.
     *
     * @param display the display to add navigation bar on.
     */
    @VisibleForTesting
    void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
        if (display == null) {
            return;
        }

        final int displayId = display.getDisplayId();
        final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId();

        if (!shouldCreateNavBarAndTaskBar(displayId)) {
            return;
        }

        // We may show TaskBar on the default display for large screen device. Don't need to create
        // navigation bar for this case.
        if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
            return;
        }

        final Context context = isOnDefaultDisplay
                ? mContext
                : mContext.createDisplayContext(display);
        NavigationBarComponent component = mNavigationBarComponentFactory.create(
                context, savedState);
        NavigationBar navBar = component.getNavigationBar();
        navBar.init();
        mNavigationBars.put(displayId, navBar);

        navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                if (result != null) {
                    navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
                            result.mImeWindowVis, result.mImeBackDisposition,
                            result.mShowImeSwitcher);
                }
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                v.removeOnAttachStateChangeListener(this);
            }
        });
    }

在这里插入图片描述
这个方法确保了在适当的条件下为每个显示设备创建导航栏。

    private boolean shouldCreateNavBarAndTaskBar(int displayId) {
        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();

        try {
            return wms.hasNavigationBar(displayId);
        } catch (RemoteException e) {
            // Cannot get wms, just return false with warning message.
            Log.w(TAG, "Cannot get WindowManager.");
            return false;
        }
    }

调用IWindowManager的hasNavigationBar方法来判断系统是否存在导航栏

NavigationBarComponent.java
package com.android.systemui.navigationbar;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import android.content.Context;
import android.os.Bundle;

import androidx.annotation.Nullable;

import com.android.systemui.dagger.qualifiers.DisplayId;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

import javax.inject.Scope;

import dagger.BindsInstance;
import dagger.Subcomponent;

/**
 * Subcomponent for a NavigationBar.
 *
 * Generally creatd on a per-display basis.
 */
@Subcomponent(modules = { NavigationBarModule.class })
@NavigationBarComponent.NavigationBarScope
public interface NavigationBarComponent {

    /** Factory for {@link NavigationBarComponent}. */
    @Subcomponent.Factory
    interface Factory {
        NavigationBarComponent create(
                @BindsInstance @DisplayId Context context,
                @BindsInstance @Nullable Bundle savedState);
    }

    /** */
    NavigationBar getNavigationBar();

    /**
     * Scope annotation for singleton items within the NavigationBarComponent.
     */
    @Documented
    @Retention(RUNTIME)
    @Scope
    @interface NavigationBarScope {}
}

NavigationBarModule.java
package com.android.systemui.navigationbar;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;

import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;

import dagger.Module;
import dagger.Provides;

/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */
@Module
public interface NavigationBarModule {
    /** A Layout inflater specific to the display's context. */
    @Provides
    @NavigationBarScope
    @DisplayId
    static LayoutInflater provideLayoutInflater(@DisplayId Context context) {
        return LayoutInflater.from(context);
    }

    /** */
    @Provides
    @NavigationBarScope
    static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
        return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
    }

    /** */
    @Provides
    @NavigationBarScope
    static NavigationBarView provideNavigationBarview(
            @DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
        return barView.findViewById(R.id.navigation_bar_view);
    }

    /** */
    @Provides
    @NavigationBarScope
    static EdgeBackGestureHandler provideEdgeBackGestureHandler(
            EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
        return factory.create(context);
    }

    /** A WindowManager specific to the display's context. */
    @Provides
    @NavigationBarScope
    @DisplayId
    static WindowManager provideWindowManager(@DisplayId Context context) {
        return context.getSystemService(WindowManager.class);
    }
}

在这里插入图片描述



navigation_bar.xml
<com.android.systemui.navigationbar.NavigationBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/navigation_bar_view"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:background="@drawable/system_bar_background">

    <com.android.systemui.navigationbar.NavigationBarInflaterView
        android:id="@+id/navigation_inflater"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false" />

</com.android.systemui.navigationbar.NavigationBarView>

在这里插入图片描述

在这里插入图片描述

NavigationBarInflaterView.java
    private void inflateChildren() {
        removeAllViews();
        mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,
                this /* root */, false /* attachToRoot */);
        addView(mHorizontal);
        mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,
                this /* root */, false /* attachToRoot */);
        addView(mVertical);
        updateAlternativeOrder();
    }

在这里插入图片描述
下面以navigation_layout.xml文件为例

navigation_layout.xml
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginStart="@dimen/rounded_corner_content_padding"
    android:layout_marginEnd="@dimen/rounded_corner_content_padding"
    android:paddingStart="@dimen/nav_content_padding"
    android:paddingEnd="@dimen/nav_content_padding"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:id="@+id/horizontal">

    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
        android:id="@+id/nav_buttons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        systemui:isVertical="false">

        <LinearLayout
            android:id="@+id/ends_group"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:clipChildren="false" />

        <LinearLayout
            android:id="@+id/center_group"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:clipChildren="false" />

    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>

</FrameLayout>

在这里插入图片描述

NavigationBarInflaterView.java
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();
        clearViews();
        inflateLayout(getDefaultLayout());
    }

    private void inflateChildren() {
        removeAllViews();
        mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,
                this /* root */, false /* attachToRoot */);
        addView(mHorizontal);
        mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,
                this /* root */, false /* attachToRoot */);
        addView(mVertical);
        updateAlternativeOrder();
    }

在这里插入图片描述
addView在class ReverseLinearLayout extends LinearLayout中实现了方法的重写
addView(mHorizontal)将 mHorizontal 视图添加到当前 ViewGroup 中

    protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
        if (sets.length != 3) {
            Log.d(TAG, "Invalid layout.");
            newLayout = getDefaultLayout();
            sets = newLayout.split(GRAVITY_SEPARATOR, 3);
        }
        String[] start = sets[0].split(BUTTON_SEPARATOR);
        String[] center = sets[1].split(BUTTON_SEPARATOR);
        String[] end = sets[2].split(BUTTON_SEPARATOR);
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mHorizontal.findViewById(R.id.ends_group),
                false /* landscape */, true /* start */);
        inflateButtons(start, mVertical.findViewById(R.id.ends_group),
                true /* landscape */, true /* start */);

        inflateButtons(center, mHorizontal.findViewById(R.id.center_group),
                false /* landscape */, false /* start */);
        inflateButtons(center, mVertical.findViewById(R.id.center_group),
                true /* landscape */, false /* start */);

        addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));
        addGravitySpacer(mVertical.findViewById(R.id.ends_group));

        inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),
                false /* landscape */, false /* start */);
        inflateButtons(end, mVertical.findViewById(R.id.ends_group),
                true /* landscape */, false /* start */);

        updateButtonDispatchersCurrentView();
    }

在这里插入图片描述

    private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
            boolean start) {
        for (int i = 0; i < buttons.length; i++) {
            inflateButton(buttons[i], parent, landscape, start);
        }
    }

    private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
        if (layoutParams instanceof LinearLayout.LayoutParams) {
            return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
                    ((LinearLayout.LayoutParams) layoutParams).weight);
        }
        return new LayoutParams(layoutParams.width, layoutParams.height);
    }

    @Nullable
    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        View v = createView(buttonSpec, parent, inflater);
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);
        parent.addView(v);
        addToDispatchers(v);
        View lastView = landscape ? mLastLandscape : mLastPortrait;
        View accessibilityView = v;
        if (v instanceof ReverseRelativeLayout) {
            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
        }
        if (lastView != null) {
            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
        }
        if (landscape) {
            mLastLandscape = accessibilityView;
        } else {
            mLastPortrait = accessibilityView;
        }
        return v;
    }

在这里插入图片描述

    View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
        String button = extractButton(buttonSpec);
        if (LEFT.equals(button)) {
            button = extractButton(NAVSPACE);
        } else if (RIGHT.equals(button)) {
            button = extractButton(MENU_IME_ROTATE);
        }
        if (HOME.equals(button)) {
            v = inflater.inflate(R.layout.home, parent, false);
        } else if (BACK.equals(button)) {
            v = inflater.inflate(R.layout.back, parent, false);
        } else if (RECENT.equals(button)) {
            v = inflater.inflate(R.layout.recent_apps, parent, false);
        } else if (MENU_IME_ROTATE.equals(button)) {
            v = inflater.inflate(R.layout.menu_ime, parent, false);
        } else if (NAVSPACE.equals(button)) {
            v = inflater.inflate(R.layout.nav_key_space, parent, false);
        } else if (CLIPBOARD.equals(button)) {
            v = inflater.inflate(R.layout.clipboard, parent, false);
        } else if (CONTEXTUAL.equals(button)) {
            v = inflater.inflate(R.layout.contextual, parent, false);
        } else if (HOME_HANDLE.equals(button)) {
            v = inflater.inflate(R.layout.home_handle, parent, false);
        } else if (IME_SWITCHER.equals(button)) {
            v = inflater.inflate(R.layout.ime_switcher, parent, false);
        } else if (button.startsWith(KEY)) {
            String uri = extractImage(button);
            int code = extractKeycode(button);
            v = inflater.inflate(R.layout.custom_key, parent, false);
            ((KeyButtonView) v).setCode(code);
            if (uri != null) {
                if (uri.contains(":")) {
                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
                } else if (uri.contains("/")) {
                    int index = uri.indexOf('/');
                    String pkg = uri.substring(0, index);
                    int id = Integer.parseInt(uri.substring(index + 1));
                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
                }
            }
        }
        return v;
    }

在这里插入图片描述


在这里插入图片描述
KeyButtonView类是一个自定义视图,用于创建导航栏中的按键,如 Home、Back、Recent Apps等。这个类扩展了 ImageView 以提供额外的功能,如处理触摸事件、播放声音效果、发送按键事件等。
在这里插入图片描述
在这里插入图片描述

launcher.xml
<com.android.launcher3.LauncherRootView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.android.launcher3.dragndrop.DragLayer
        android:id="@+id/drag_layer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:importantForAccessibility="no">

        <com.android.launcher3.views.AccessibilityActionsView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="@string/home_screen"
            />

        <!-- The workspace contains 5 screens of cells -->
        <!-- DO NOT CHANGE THE ID -->
        <com.android.launcher3.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:theme="@style/HomeScreenElementTheme"
            launcher:pageIndicator="@+id/page_indicator" />

        <!-- DO NOT CHANGE THE ID -->
        <include
            android:id="@+id/hotseat"
            layout="@layout/hotseat" />

        <!-- Keep these behind the workspace so that they are not visible when
         we go into AllApps -->
        <com.android.launcher3.pageindicators.WorkspacePageIndicator
            android:id="@+id/page_indicator"
            android:layout_width="match_parent"
            android:layout_height="@dimen/workspace_page_indicator_height"
            android:layout_gravity="bottom|center_horizontal"
            android:theme="@style/HomeScreenElementTheme" />

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

        <com.android.launcher3.views.ScrimView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/scrim_view"
            android:background="@android:color/transparent" />

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

        <include
            android:id="@+id/apps_view"
            layout="@layout/all_apps"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.android.launcher3.dragndrop.DragLayer>

</com.android.launcher3.LauncherRootView>

在这里插入图片描述

overview_panel.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">


    <com.android.quickstep.views.LauncherRecentsView
        android:id="@+id/overview_panel"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:accessibilityPaneTitle="@string/accessibility_recent_apps"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:visibility="invisible" />

    <include
        android:id="@+id/overview_actions_view"
        layout="@layout/overview_actions_container" />

</merge>

overview_actions_container.xml
<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="20dp"
    android:layout_gravity="center_horizontal|bottom">

    <LinearLayout
        android:id="@+id/action_buttons"
        android:layout_width="match_parent"
        android:layout_height="@dimen/overview_actions_height"
        android:layout_gravity="bottom|center_horizontal"
        android:orientation="horizontal">

        <Space
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1" />

        <Button
            android:id="@+id/action_screenshot"
            style="@style/OverviewActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableStart="@drawable/ic_screenshot"
            android:text="@string/action_screenshot"
            android:theme="@style/ThemeControlHighlightWorkspaceColor" />

        <Space
            android:id="@+id/action_split_space"
            android:layout_width="@dimen/overview_actions_button_spacing"
            android:layout_height="1dp"
            android:visibility="gone" />

        <Button
            android:id="@+id/action_split"
            style="@style/OverviewActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/action_split"
            android:theme="@style/ThemeControlHighlightWorkspaceColor"
            android:visibility="gone" />

        <Space
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1" />

        <Space
            android:id="@+id/oav_three_button_space"
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1"
            android:visibility="gone" />
    </LinearLayout>

</com.android.quickstep.views.OverviewActionsView>

在这里插入图片描述



 RecentView.java
    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            BaseActivityInterface sizeStrategy) {
        super(context, attrs, defStyleAttr);
        setEnableFreeScroll(true);
        mSizeStrategy = sizeStrategy;
        mActivity = BaseActivity.fromContext(context);
        mOrientationState = new RecentsOrientedState(
                context, mSizeStrategy, this::animateRecentsRotationInPlace);
        final int rotation = mActivity.getDisplay().getRotation();
        mOrientationState.setRecentsRotation(rotation);

        mScrollHapticMinGapMillis = getResources()
                .getInteger(R.integer.recentsScrollHapticMinGapMillis);
        mFastFlingVelocity = getResources()
                .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
        mModel = RecentsModel.INSTANCE.get(context);
        mIdp = InvariantDeviceProfile.INSTANCE.get(context);

        mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                .inflate(R.layout.overview_clear_all_button, this, false);
        mClearAllButton.setOnClickListener(this::dismissAllTasks);
        mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                10 /* initial size */);
        mGroupedTaskViewPool = new ViewPool<>(context, this,
                R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
        mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
                5 /* max size */, 1 /* initial size */);

        mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
        setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
        mSplitPlaceholderSize = getResources().getDimensionPixelSize(
                R.dimen.split_placeholder_size);
        mSplitPlaceholderInset = getResources().getDimensionPixelSize(
                R.dimen.split_placeholder_inset);
        mSquaredTouchSlop = squaredTouchSlop(context);
        mClampedScrollOffsetBound = getResources().getDimensionPixelSize(
                R.dimen.transient_taskbar_clamped_offset_bound);

        mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
        mEmptyIcon.setCallback(this);
        mEmptyMessage = context.getText(R.string.recents_empty_message);
        mEmptyMessagePaint = new TextPaint();
        mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
        mEmptyMessagePaint.setTextSize(getResources()
                .getDimension(R.dimen.recents_empty_message_text_size));
        mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
                Typeface.NORMAL));
        mEmptyMessagePaint.setAntiAlias(true);
        mEmptyMessagePadding = getResources()
                .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
        setWillNotDraw(false);
        updateEmptyMessage();
        mOrientationHandler = mOrientationState.getOrientationHandler();

        mTaskOverlayFactory = Overrides.getObject(
                TaskOverlayFactory.class,
                context.getApplicationContext(),
                R.string.task_overlay_factory_class);

        // Initialize quickstep specific cache params here, as this is constructed only once
        mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);

        mTintingColor = getForegroundScrimDimColor(context);

        // if multi-instance feature is enabled
        if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
            // invalidate the current list of tasks if filter changes with a fading in/out animation
            mFilterState.setOnFilterUpdatedListener(() -> {
                Animator animatorFade = mActivity.getStateManager().createStateElementAnimation(
                        RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 1f, 0f);
                Animator animatorAppear = mActivity.getStateManager().createStateElementAnimation(
                        RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 0f, 1f);
                animatorFade.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(@NonNull Animator animation) {
                        RecentsView.this.invalidateTaskList();
                        updateClearAllFunction();
                        reloadIfNeeded();
                        if (mPendingAnimation != null) {
                            mPendingAnimation.addEndListener(success -> {
                                animatorAppear.start();
                            });
                        } else {
                            animatorAppear.start();
                        }
                    }
                });
                animatorFade.start();
            });
        }
        // make sure filter is turned off by default
        mFilterState.setFilterBy(null);
    }

在这里插入图片描述
当“清除所有”按钮被点击时,会调用 dismissAllTasks 方法来关闭所有任务。
通过创建任务视图池实例来加载布局。

task.xml
<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
     file, they need to be loaded at runtime. -->
<com.android.quickstep.views.TaskView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:defaultFocusHighlightEnabled="false"
    android:focusable="true"
    launcher:borderColor="?androidprv:attr/materialColorOutline">

    <com.android.quickstep.views.TaskThumbnailView
        android:id="@+id/snapshot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
         separately through RecentsView#resetFromSplitSelectionState() -->
    <ImageView
        android:id="@+id/show_windows"
        android:layout_height="@dimen/recents_filter_icon_size"
        android:layout_width="@dimen/recents_filter_icon_size"
        android:layout_gravity="end"
        android:alpha="0"
        android:tint="@color/recents_filter_icon"
        android:importantForAccessibility="no"
        android:src="@drawable/ic_select_windows" />

    <com.android.quickstep.views.IconView
        android:id="@+id/icon"
        android:layout_width="@dimen/task_thumbnail_icon_size"
        android:layout_height="@dimen/task_thumbnail_icon_size"
        android:focusable="false"
        android:importantForAccessibility="no"/>
</com.android.quickstep.views.TaskView>

在这里插入图片描述

TaskView.java
    protected void setIcon(IconView iconView, @Nullable Drawable icon) {
        if (icon != null) {
            iconView.setDrawable(icon);
            iconView.setOnClickListener(v -> {
                if (confirmSecondSplitSelectApp()) {
                    return;
                }
                showTaskMenu(iconView);
            });
            iconView.setOnLongClickListener(v -> {
                requestDisallowInterceptTouchEvent(true);
                return showTaskMenu(iconView);
            });
        } else {
            iconView.setDrawable(null);
            iconView.setOnClickListener(null);
            iconView.setOnLongClickListener(null);
        }
    }

在这里插入图片描述
在这里插入图片描述

F ---------->内容补充说明


在 Android 11 中,最近任务( Recent Apps)的响应机制有所变化,它不再完全由 SystemUI 处理,而是部分集成到了 Launcher3 应用中。以下是 SystemUI 如何响应最近任务请求的概述:

初始化阶段:SystemUI 服务启动时,会初始化 Recents 组件。这通常涉及到 Recents.java 文件中的 Recents 类,它继承自 SystemUI。它实现了 CoreStartable 和 CommandQueue.Callbacks 接口。在 start() 方法中,会将 Recents 实例注册到 CommandQueue 中,并启动 RecentsImplementation 的实现类,如 OverviewProxyRecentsImpl。

处理用户交互:当用户通过手势或按键触发最近任务时,事件会传递到 PhoneWindowManager,然后调用 SystemUI 中的 Recents 组件的 toggleRecentApps() 方法。

OverviewProxyRecentsImpl:在 OverviewProxyRecentsImpl.java 中,toggleRecentApps() 方法会被调用。如果与 Launcher3 的服务连接成功,它会通过 OverviewProxyService 调用 Launcher3 中的 IOverviewProxy 接口的 onOverviewToggle() 方法。

Launcher3 中的 TouchInteractionService:在 Launcher3 应用中,TouchInteractionService.java 包含了 onOverviewToggle() 方法的实现。这个方法会触发最近任务界面的显示。

RecentsActivityCommand:最终,RecentsActivityCommand 会被执行,它负责启动 RecentsActivity,这是显示最近任务列表的界面。

显示最近任务:RecentsActivity 会显示最近任务列表,并且可能会涉及到 RecentsView 类,它负责显示任务的缩略图和提供用户交互。

在 Android 11 中,由于最近任务界面的实现部分被移动到了 Launcher3 应用中,因此如果替换了默认的 Launcher3,可能会导致最近任务功能失效。这是因为新的 Launcher 可能没有集成处理最近任务的逻辑。



OverviewProxyRecentsImpl.java 中的 toggleRecentApps 方法是处理最近任务(Recent Apps)切换的关键方法。当用户想要查看最近任务时,这个动作会被触发。

以下是 toggleRecentApps 方法的工作流程:

toggleRecentApps 方法首先会检查是否已经连接到了 Launcher3 的服务。如果是,它会将切换逻辑交给 Launcher3 处理。

如果连接到了 Launcher3 的服务,它会通过 OverviewProxyService 的 getProxy 方法获取到 IOverviewProxy 的实例。

然后,它会尝试调用 IOverviewProxy 实例的 onOverviewToggle 方法来切换最近任务的显示状态。

在 Launcher3 中,TouchInteractionService 会接收到这个切换请求,并调用 OverviewCommandHelper 的 onOverviewToggle 方法。

onOverviewToggle 方法会调用 ActivityManagerWrapper 的 closeSystemWindows 方法,传入 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS 作为参数,这会导致所有前台的系统窗口关闭,为显示最近任务界面做准备。

最后,MAIN_EXECUTOR 会执行 RecentsActivityCommand,这是一个 Runnable 对象,负责启动 RecentsActivity,也就是最近任务的界面。

RecentsActivity 会设置视图并初始化 FallbackRecentsView,这是显示最近任务列表的主要视图。

当 RecentsView 附加到窗口时,它会注册任务堆栈变化的监听器,以便在任务发生变化时更新 UI。
在这里插入图片描述



TaskUtils.closeSystemWindowsAsync 是一个在 Android 系统中用于关闭系统窗口的异步方法。在 Android 11 版本中,最近任务(Recent Apps)的功能与 SystemUI 和 Launcher3 都有关联。当用户想要查看最近任务时,SystemUI 会通过 CommandQueue 的 showRecentApps 方法来触发这一动作。这个方法最终会调用到 IOverviewProxy 接口的 onOverviewShown 方法,而 Launcher3 中的 TouchInteractionService 服务的 TISBinder 对象实现了这个接口。

在 TISBinder 的 onOverviewShown 方法中,如果是因为用户按下最近任务键(通常是方块键)来触发的,那么会调用 TaskUtils.closeSystemWindowsAsync 方法来异步关闭系统窗口,并且传递一个原因参数 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS。这样做是为了在显示最近任务视图之前,关闭其他所有前台活动窗口,确保用户界面的整洁和最近任务视图的突出显示。

在这里插入图片描述



在这里插入图片描述


Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐