0


Android14---SystemUI分析(代码、图文详细讲解)

Android14---SystemUI分析(代码、图文详细讲解)

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

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

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

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

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

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

1、锁屏页面

status_bar_expanded.xml
<com.android.systemui.shade.NotificationPanelViewxmlns: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.LongPressHandlingViewandroid:id="@+id/keyguard_long_press"android:layout_width="match_parent"android:layout_height="match_parent"/><ViewStubandroid:id="@+id/keyguard_qs_user_switch_stub"android:layout="@layout/keyguard_qs_user_switch"android:layout_height="match_parent"android:layout_width="match_parent"/><includelayout="@layout/status_bar_expanded_plugin_frame"/><com.android.systemui.shade.NotificationsQuickSettingsContainerandroid: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"><includelayout="@layout/keyguard_status_view"android:visibility="gone"/><includelayout="@layout/dock_info_overlay"/><FrameLayoutandroid: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 --><ViewStubandroid:id="@+id/qs_header_stub"android:layout_height="wrap_content"android:layout_width="match_parent"/><androidx.constraintlayout.widget.Guidelineandroid: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. --><includelayout="@layout/notification_stack_scroll_layout"/><includelayout="@layout/photo_preview_overlay"/><includelayout="@layout/keyguard_status_bar"android:visibility="invisible"/><Buttonandroid: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.TapAgainViewandroid: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><ViewStubandroid:id="@+id/keyguard_user_switcher_stub"android:layout="@layout/keyguard_user_switcher"android:layout_height="match_parent"android:layout_width="match_parent"/><includelayout="@layout/dock_info_bottom_area_overlay"/><com.android.keyguard.LockIconViewandroid:id="@+id/lock_icon_view"android:layout_width="wrap_content"android:layout_height="wrap_content"><!-- Background protection --><ImageViewandroid:id="@+id/lock_icon_bg"android:layout_width="match_parent"android:layout_height="match_parent"android:src="@drawable/fingerprint_bg"android:visibility="invisible"/><ImageViewandroid: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><includelayout="@layout/keyguard_bottom_area"android:visibility="gone"/></com.android.systemui.shade.NotificationPanelView>

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

keyguard_status_bar.xml
<!-- Extends RelativeLayout --><com.android.systemui.statusbar.phone.KeyguardStatusBarViewxmlns: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"><LinearLayoutandroid: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"><includeandroid: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"/><FrameLayoutandroid: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"><includelayout="@layout/system_icons"/></FrameLayout><ImageViewandroid: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><Spaceandroid:id="@+id/cutout_space_view"android:layout_width="0dp"android:layout_height="match_parent"android:gravity="center"android:visibility="gone"/><com.android.keyguard.CarrierTextandroid: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.KeyguardPatternViewxmlns: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"><includelayout="@layout/keyguard_bouncer_message_area"/><com.android.systemui.keyguard.bouncer.ui.BouncerMessageViewandroid:id="@+id/bouncer_message_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"/><androidx.constraintlayout.widget.ConstraintLayoutandroid: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.Guidelineandroid: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.LockPatternViewandroid: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><includelayout="@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.NotificationPanelViewxmlns: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.LongPressHandlingViewandroid:id="@+id/keyguard_long_press"android:layout_width="match_parent"android:layout_height="match_parent"/><ViewStubandroid:id="@+id/keyguard_qs_user_switch_stub"android:layout="@layout/keyguard_qs_user_switch"android:layout_height="match_parent"android:layout_width="match_parent"/><includelayout="@layout/status_bar_expanded_plugin_frame"/><com.android.systemui.shade.NotificationsQuickSettingsContainerandroid: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"><includelayout="@layout/keyguard_status_view"android:visibility="gone"/><includelayout="@layout/dock_info_overlay"/><FrameLayoutandroid: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 --><ViewStubandroid:id="@+id/qs_header_stub"android:layout_height="wrap_content"android:layout_width="match_parent"/><androidx.constraintlayout.widget.Guidelineandroid: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. --><includelayout="@layout/notification_stack_scroll_layout"/><includelayout="@layout/photo_preview_overlay"/><includelayout="@layout/keyguard_status_bar"android:visibility="invisible"/><Buttonandroid: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.TapAgainViewandroid: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><ViewStubandroid:id="@+id/keyguard_user_switcher_stub"android:layout="@layout/keyguard_user_switcher"android:layout_height="match_parent"android:layout_width="match_parent"/><includelayout="@layout/dock_info_bottom_area_overlay"/><com.android.keyguard.LockIconViewandroid:id="@+id/lock_icon_view"android:layout_width="wrap_content"android:layout_height="wrap_content"><!-- Background protection --><ImageViewandroid:id="@+id/lock_icon_bg"android:layout_width="match_parent"android:layout_height="match_parent"android:src="@drawable/fingerprint_bg"android:visibility="invisible"/><ImageViewandroid: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><includelayout="@layout/keyguard_bottom_area"android:visibility="gone"/></com.android.systemui.shade.NotificationPanelView>

在这里插入图片描述

qs_panel.xml
<com.android.systemui.qs.QSContainerImplxmlns: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.NonInterceptingScrollViewandroid: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.QSPanelandroid: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"><includelayout="@layout/qs_footer_impl"/></com.android.systemui.qs.QSPanel></com.android.systemui.qs.NonInterceptingScrollView><includelayout="@layout/quick_status_bar_expanded_header"/><includelayout="@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"/><includeandroid: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.QSFooterViewxmlns: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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/qs_footer_height"android:layout_gravity="center_vertical"><TextViewandroid: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.PageIndicatorandroid:id="@+id/footer_page_indicator"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_gravity="center_vertical"android:visibility="gone"/><FrameLayoutandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"><com.android.systemui.statusbar.AlphaOptimizedImageViewandroid: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.NoRemeasureMotionLayoutxmlns: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.Guidelineandroid: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.Guidelineandroid: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.Guidelineandroid: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.Guidelineandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/center_left"android:orientation="vertical"/><androidx.constraintlayout.widget.Guidelineandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/center_right"android:orientation="vertical"/><androidx.constraintlayout.widget.Barrierandroid: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.Clockandroid: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.VariableDateViewandroid: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"/><includeandroid: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.StatusIconContainerandroid: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.BatteryMeterViewandroid: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"/><FrameLayoutandroid: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.OngoingPrivacyChipandroid: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.PhoneStatusBarViewxmlns: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"><ImageViewandroid: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"/><LinearLayoutandroid: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. --><FrameLayoutandroid: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. --><FrameLayoutandroid:id="@+id/status_bar_start_side_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:clipChildren="false"><includelayout="@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 --><LinearLayoutandroid:id="@+id/status_bar_start_side_except_heads_up"android:layout_height="wrap_content"android:layout_width="match_parent"android:clipChildren="false"><ViewStubandroid:id="@+id/operator_name"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout="@layout/operator_name"/><com.android.systemui.statusbar.policy.Clockandroid: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"/><includelayout="@layout/ongoing_call_chip"/><com.android.systemui.statusbar.AlphaOptimizedFrameLayoutandroid: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.Spaceandroid: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. --><FrameLayoutandroid: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.AlphaOptimizedLinearLayoutandroid: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"><includeandroid: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"/><includelayout="@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.QSContainerImplxmlns: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.NonInterceptingScrollViewandroid: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.QSPanelandroid: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"><includelayout="@layout/qs_footer_impl"/></com.android.systemui.qs.QSPanel></com.android.systemui.qs.NonInterceptingScrollView><includelayout="@layout/quick_status_bar_expanded_header"/><includelayout="@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"/><includeandroid: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.QSCustomizerxmlns: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
publicQSCustomizer(Context context,AttributeSet attrs){super(context, attrs);LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content,this);
        mClipper =newQSDetailClipper(findViewById(R.id.customize_container));Toolbar toolbar =findViewById(com.android.internal.R.id.action_bar);TypedValue value =newTypedValue();
        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 =newDefaultItemAnimator();
        animator.setMoveDuration(TileAdapter.MOVE_DURATION);
        mRecyclerView.setItemAnimator(animator);updateTransparentViewHeight();}

在这里插入图片描述

qs_customize_panel_content.xml
<mergexmlns:android="http://schemas.android.com/apk/res/android">->
    <Viewandroid: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.AlphaOptimizedLinearLayoutandroid: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"><Toolbarandroid: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.RecyclerViewandroid: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><Viewandroid: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.BrightnessSliderViewxmlns: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.ToggleSeekBarandroid: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@OverrideprotectedvoidonFinishInflate(){super.onFinishInflate();setLayerType(LAYER_TYPE_HARDWARE,null);

        mSlider =requireViewById(R.id.slider);
        mSlider.setAccessibilityLabel(getContentDescription().toString());// Finds the progress drawable. Assumes brightness_progress_drawable.xmltry{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
privatevolatileboolean mAutomatic;// Brightness adjusted automatically using ambient light.privatevolatileboolean mIsVrModeEnabled;privateboolean mListening;privateboolean mExternalChange;privateboolean mControlValueInitialized;privatefloat mBrightnessMin =PowerManager.BRIGHTNESS_MIN;privatefloat mBrightnessMax =PowerManager.BRIGHTNESS_MAX;
mAutomatic:屏幕亮度是否根据环境光传感器自动调整。
                  
mListening:是否正在监听外部事件

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

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

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

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

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

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

BrightnessController.java
privatevoidupdateSlider(float brightnessValue,boolean inVrMode){finalfloat min = mBrightnessMin;finalfloat 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-1if(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 CH108Q20220602finalint 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 --><stringname="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
finalvoidaddTile(QSPanelControllerBase.TileRecord tileRecord){finalQSTile.Callback callback =newQSTile.Callback(){@OverridepublicvoidonStateChanged(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
overridefuninit(tile: QSTile){init({ v: View?-> tile.click(this)},{ view: View?->
                    tile.longClick(this)true})}privatefuninit(
        click: OnClickListener?,
        longClick: OnLongClickListener?){setOnClickListener(click)
        onLongClickListener = longClick
    }

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

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

QSTileImpl.java
publicvoidclick(@NullableView view){
        mMetricsLogger.write(populate(newLogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION).addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK,0,getMetricsSpec(),getInstanceId());finalint eventId = mClickEventId++;
        mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                eventId);if(!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)){
            mHandler.obtainMessage(H.CLICK, eventId,0, view).sendToTarget();}}publicvoidsecondaryClick(@NullableView view){
        mMetricsLogger.write(populate(newLogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION).addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK,0,getMetricsSpec(),getInstanceId());finalint eventId = mClickEventId++;
        mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
                mState.state, eventId);
        mHandler.obtainMessage(H.SECONDARY_CLICK, eventId,0, view).sendToTarget();}@OverridepublicvoidlongClick(@NullableView view){
        mMetricsLogger.write(populate(newLogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION).addTaggedData(FIELD_STATUS_BAR_STATE,
                        mStatusBarStateController.getState())));
        mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS,0,getMetricsSpec(),getInstanceId());finalint eventId = mClickEventId++;
        mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                eventId);
        mHandler.obtainMessage(H.LONG_CLICK, eventId,0, view).sendToTarget();}

在这里插入图片描述

QSTileImpl.java
protectedfinalclassHextendsHandler{privatestaticfinalintADD_CALLBACK=1;privatestaticfinalintCLICK=2;privatestaticfinalintSECONDARY_CLICK=3;privatestaticfinalintLONG_CLICK=4;privatestaticfinalintREFRESH_STATE=5;privatestaticfinalintUSER_SWITCH=6;privatestaticfinalintDESTROY=7;privatestaticfinalintREMOVE_CALLBACKS=8;privatestaticfinalintREMOVE_CALLBACK=9;privatestaticfinalintSET_LISTENING=10;@VisibleForTestingprotectedstaticfinalintSTALE=11;privatestaticfinalintINITIALIZE=12;@VisibleForTestingprotectedH(Looper looper){super(looper);}@OverridepublicvoidhandleMessage(Message msg){String name =null;try{if(msg.what ==ADD_CALLBACK){
                    name ="handleAddCallback";handleAddCallback((QSTile.Callback) msg.obj);}elseif(msg.what ==REMOVE_CALLBACKS){
                    name ="handleRemoveCallbacks";handleRemoveCallbacks();}elseif(msg.what ==REMOVE_CALLBACK){
                    name ="handleRemoveCallback";handleRemoveCallback((QSTile.Callback) msg.obj);}elseif(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);}}elseif(msg.what ==SECONDARY_CLICK){
                    name ="handleSecondaryClick";
                    mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);handleSecondaryClick((View) msg.obj);}elseif(msg.what ==LONG_CLICK){
                    name ="handleLongClick";
                    mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);handleLongClick((View) msg.obj);}elseif(msg.what ==REFRESH_STATE){
                    name ="handleRefreshState";handleRefreshState(msg.obj);}elseif(msg.what ==USER_SWITCH){
                    name ="handleUserSwitch";handleUserSwitch(msg.arg1);}elseif(msg.what ==DESTROY){
                    name ="handleDestroy";handleDestroy();}elseif(msg.what ==SET_LISTENING){
                    name ="handleSetListeningInternal";handleSetListeningInternal(msg.obj, msg.arg1 !=0);}elseif(msg.what ==STALE){
                    name ="handleStale";handleStale();}elseif(msg.what ==INITIALIZE){
                    name ="initialize";handleInitialize();}else{thrownewIllegalArgumentException("Unknown msg: "+ msg.what);}}catch(Throwable t){finalString error ="Error in "+ name;Log.w(TAG, error, t);}}}

在这里插入图片描述

protectedfinalvoidhandleRefreshState(@NullableObject 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);}privatevoidhandleStateChanged(){if(mCallbacks.size()!=0){for(int i =0; i < mCallbacks.size(); i++){
                mCallbacks.get(i).onStateChanged(mState);}}}

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

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

BluetoothTile.java
@OverrideprotectedvoidhandleUpdateState(BooleanState state,Object arg){checkIfRestrictionEnforcedByAdminOnly(state,UserManager.DISALLOW_BLUETOOTH);finalboolean transientEnabling = arg ==ARG_SHOW_TRANSIENT_ENABLING;finalboolean enabled = transientEnabling || mController.isBluetoothEnabled();finalboolean connected = mController.isBluetoothConnected();finalboolean 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 =newSlashState();}
        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;}elseif(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
privatevoidhandleStateChanged(){if(mCallbacks.size()!=0){for(int i =0; i < mCallbacks.size(); i++){
                mCallbacks.get(i).onStateChanged(mState);}}}
QSPanel.java
finalvoidaddTile(QSPanelControllerBase.TileRecord tileRecord){finalQSTile.Callback callback =newQSTile.Callback(){@OverridepublicvoidonStateChanged(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
publicabstractvoidonStateChanged(State state);

在这里插入图片描述

QSTileViewImpl.kt
overridefunonStateChanged(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 方法,状态更新的操作将在适当的时机异步执行。

innerclassStateChangeRunnable(privateval state: QSTile.State): Runnable {overridefunrun(){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 viewoverridefunequals(other: Any?): Boolean {return other is StateChangeRunnable
        }// This makes sure that all instances have the same hashcode (because they are `equal`)overridefunhashCode(): Int {return StateChangeRunnable::class.hashCode()}}

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

// HANDLE STATE CHANGES RELATED METHODSprotectedopenfunhandleStateChanged(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 descriptionval 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
            }}//// Labelsif(!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
            }}// Colorsif(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 iconloadSideViewDrawableIfNecessary(state)

        label.isEnabled =!state.disabledByPolicy

        lastState = state.state
        lastDisabledByPolicy = state.disabledByPolicy
    }

在这里插入图片描述
在这里插入图片描述
至此,就将开关点击背后的流程梳理清楚了

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

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

CentralSurfacesImpl.java

在这里插入图片描述

@OverridepublicvoidcreateAndAddWindows(@NullableRegisterStatusBarResult result){makeStatusBarView(result);
        mNotificationShadeWindowController.attach();
        mStatusBarWindowController.attach();}

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

protectedvoidcreateNavigationBar(@NullableRegisterStatusBarResult 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.
     */@VisibleForTestingvoidcreateNavigationBar(Display display,Bundle savedState,RegisterStatusBarResult result){if(display ==null){return;}finalint displayId = display.getDisplayId();finalboolean 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;}finalContext context = isOnDefaultDisplay
                ? mContext
                : mContext.createDisplayContext(display);NavigationBarComponent component = mNavigationBarComponentFactory.create(
                context, savedState);NavigationBar navBar = component.getNavigationBar();
        navBar.init();
        mNavigationBars.put(displayId, navBar);

        navBar.addOnAttachStateChangeListener(newView.OnAttachStateChangeListener(){@OverridepublicvoidonViewAttachedToWindow(View v){if(result !=null){
                    navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
                            result.mImeWindowVis, result.mImeBackDisposition,
                            result.mShowImeSwitcher);}}@OverridepublicvoidonViewDetachedFromWindow(View v){
                v.removeOnAttachStateChangeListener(this);}});}

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

privatebooleanshouldCreateNavBarAndTaskBar(int displayId){finalIWindowManager 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.");returnfalse;}}

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

NavigationBarComponent.java
packagecom.android.systemui.navigationbar;importstaticjava.lang.annotation.RetentionPolicy.RUNTIME;importandroid.content.Context;importandroid.os.Bundle;importandroidx.annotation.Nullable;importcom.android.systemui.dagger.qualifiers.DisplayId;importjava.lang.annotation.Documented;importjava.lang.annotation.Retention;importjavax.inject.Scope;importdagger.BindsInstance;importdagger.Subcomponent;/**
 * Subcomponent for a NavigationBar.
 *
 * Generally creatd on a per-display basis.
 */@Subcomponent(modules ={NavigationBarModule.class})@NavigationBarComponent.NavigationBarScopepublicinterfaceNavigationBarComponent{/** Factory for {@link NavigationBarComponent}. */@Subcomponent.FactoryinterfaceFactory{NavigationBarComponentcreate(@BindsInstance@DisplayIdContext context,@BindsInstance@NullableBundle savedState);}/** */NavigationBargetNavigationBar();/**
     * Scope annotation for singleton items within the NavigationBarComponent.
     */@Documented@Retention(RUNTIME)@Scope@interfaceNavigationBarScope{}}
NavigationBarModule.java
packagecom.android.systemui.navigationbar;importandroid.content.Context;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.view.WindowManager;importcom.android.systemui.R;importcom.android.systemui.dagger.qualifiers.DisplayId;importcom.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;importcom.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;importdagger.Module;importdagger.Provides;/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */@ModulepublicinterfaceNavigationBarModule{/** A Layout inflater specific to the display's context. */@Provides@NavigationBarScope@DisplayIdstaticLayoutInflaterprovideLayoutInflater(@DisplayIdContext context){returnLayoutInflater.from(context);}/** */@Provides@NavigationBarScopestaticNavigationBarFrameprovideNavigationBarFrame(@DisplayIdLayoutInflater layoutInflater){return(NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window,null);}/** */@Provides@NavigationBarScopestaticNavigationBarViewprovideNavigationBarview(@DisplayIdLayoutInflater layoutInflater,NavigationBarFrame frame){View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);return barView.findViewById(R.id.navigation_bar_view);}/** */@Provides@NavigationBarScopestaticEdgeBackGestureHandlerprovideEdgeBackGestureHandler(EdgeBackGestureHandler.Factory factory,@DisplayIdContext context){return factory.create(context);}/** A WindowManager specific to the display's context. */@Provides@NavigationBarScope@DisplayIdstaticWindowManagerprovideWindowManager(@DisplayIdContext context){return context.getSystemService(WindowManager.class);}}

在这里插入图片描述



navigation_bar.xml
<com.android.systemui.navigationbar.NavigationBarViewxmlns: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.NavigationBarInflaterViewandroid: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
privatevoidinflateChildren(){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
<FrameLayoutxmlns: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.NearestTouchFrameandroid:id="@+id/nav_buttons"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"systemui:isVertical="false"><LinearLayoutandroid:id="@+id/ends_group"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:clipToPadding="false"android:clipChildren="false"/><LinearLayoutandroid: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
@OverrideprotectedvoidonFinishInflate(){super.onFinishInflate();inflateChildren();clearViews();inflateLayout(getDefaultLayout());}privatevoidinflateChildren(){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 中

protectedvoidinflateLayout(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();}

在这里插入图片描述

privatevoidinflateButtons(String[] buttons,ViewGroup parent,boolean landscape,boolean start){for(int i =0; i < buttons.length; i++){inflateButton(buttons[i], parent, landscape, start);}}privateViewGroup.LayoutParamscopy(ViewGroup.LayoutParams layoutParams){if(layoutParams instanceofLinearLayout.LayoutParams){returnnewLinearLayout.LayoutParams(layoutParams.width, layoutParams.height,((LinearLayout.LayoutParams) layoutParams).weight);}returnnewLayoutParams(layoutParams.width, layoutParams.height);}@NullableprotectedViewinflateButton(String buttonSpec,ViewGroup parent,boolean landscape,boolean start){LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;View v =createView(buttonSpec, parent, inflater);if(v ==null)returnnull;

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

在这里插入图片描述

ViewcreateView(String buttonSpec,ViewGroup parent,LayoutInflater inflater){View v =null;String button =extractButton(buttonSpec);if(LEFT.equals(button)){
            button =extractButton(NAVSPACE);}elseif(RIGHT.equals(button)){
            button =extractButton(MENU_IME_ROTATE);}if(HOME.equals(button)){
            v = inflater.inflate(R.layout.home, parent,false);}elseif(BACK.equals(button)){
            v = inflater.inflate(R.layout.back, parent,false);}elseif(RECENT.equals(button)){
            v = inflater.inflate(R.layout.recent_apps, parent,false);}elseif(MENU_IME_ROTATE.equals(button)){
            v = inflater.inflate(R.layout.menu_ime, parent,false);}elseif(NAVSPACE.equals(button)){
            v = inflater.inflate(R.layout.nav_key_space, parent,false);}elseif(CLIPBOARD.equals(button)){
            v = inflater.inflate(R.layout.clipboard, parent,false);}elseif(CONTEXTUAL.equals(button)){
            v = inflater.inflate(R.layout.contextual, parent,false);}elseif(HOME_HANDLE.equals(button)){
            v = inflater.inflate(R.layout.home_handle, parent,false);}elseif(IME_SWITCHER.equals(button)){
            v = inflater.inflate(R.layout.ime_switcher, parent,false);}elseif(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));}elseif(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.LauncherRootViewxmlns: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.DragLayerandroid: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.AccessibilityActionsViewandroid: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.Workspaceandroid: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 --><includeandroid: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.WorkspacePageIndicatorandroid: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"/><includeandroid:id="@+id/drop_target_bar"layout="@layout/drop_target_bar"/><com.android.launcher3.views.ScrimViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/scrim_view"android:background="@android:color/transparent"/><includeandroid:id="@+id/overview_panel"layout="@layout/overview_panel"/><includeandroid: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
<mergexmlns:android="http://schemas.android.com/apk/res/android"><com.android.quickstep.views.LauncherRecentsViewandroid: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"/><includeandroid:id="@+id/overview_actions_view"layout="@layout/overview_actions_container"/></merge>
overview_actions_container.xml
<com.android.quickstep.views.OverviewActionsViewxmlns: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"><LinearLayoutandroid: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"><Spaceandroid:layout_width="0dp"android:layout_height="1dp"android:layout_weight="1"/><Buttonandroid: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"/><Spaceandroid:id="@+id/action_split_space"android:layout_width="@dimen/overview_actions_button_spacing"android:layout_height="1dp"android:visibility="gone"/><Buttonandroid: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"/><Spaceandroid:layout_width="0dp"android:layout_height="1dp"android:layout_weight="1"/><Spaceandroid: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
publicRecentsView(Context context,@NullableAttributeSet attrs,int defStyleAttr,BaseActivityInterface sizeStrategy){super(context, attrs, defStyleAttr);setEnableFreeScroll(true);
        mSizeStrategy = sizeStrategy;
        mActivity =BaseActivity.fromContext(context);
        mOrientationState =newRecentsOrientedState(
                context, mSizeStrategy,this::animateRecentsRotationInPlace);finalint 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 =newViewPool<>(context,this,R.layout.task,20/* max size */,10/* initial size */);
        mGroupedTaskViewPool =newViewPool<>(context,this,R.layout.task_grouped,20/* max size */,10/* initial size */);
        mDesktopTaskViewPool =newViewPool<>(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 =newTextPaint();
        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 enabledif(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(newAnimatorListenerAdapter(){@OverridepublicvoidonAnimationEnd(@NonNullAnimator 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.TaskViewxmlns: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.TaskThumbnailViewandroid: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() --><ImageViewandroid: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.IconViewandroid: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
protectedvoidsetIcon(IconView iconView,@NullableDrawable icon){if(icon !=null){
            iconView.setDrawable(icon);
            iconView.setOnClickListener(v ->{if(confirmSecondSplitSelectApp()){return;}showTaskMenu(iconView);});
            iconView.setOnLongClickListener(v ->{requestDisallowInterceptTouchEvent(true);returnshowTaskMenu(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。这样做是为了在显示最近任务视图之前,关闭其他所有前台活动窗口,确保用户界面的整洁和最近任务视图的突出显示。

在这里插入图片描述


在这里插入图片描述


标签: android

本文转载自: https://blog.csdn.net/qq_45696288/article/details/142456306
版权归原作者 爱睡觉的小馨 所有, 如有侵权,请联系我们删除。

“Android14---SystemUI分析(代码、图文详细讲解)”的评论:

还没有评论