在上两篇文章中,我们介绍SystemUI的启动过程,以及基本的组件依赖关系。基本的依赖关系请读者一定要掌握,因为后面的文章,将会时常出现这些依赖关系的使用,届时将会一笔带过,而不会详细说明他们的实现细节和原理。
接下来我们将介绍,SystemUI中各个UI部分是如何被加入屏幕中的,以及查看下拉状态中的各个图标的实现。
但是在进入真正的主题之前,我们还需要了解SystemUI中的各个UI的基本布局以及名字,只有了解了这些基本布局和名字,才能够在许多相似名字中分清楚具体的对象是谁。
接下来我们将介绍SystemUI的基本布局设计,看看整个SystemUI的布局被大致分成了哪些内容,以及这些内容对应的业务是什么。
在“Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)http://t.csdn.cn/chk6Y”文章中,我们提及过,SystemUI可以通过WindowManager来显示自己的UI。
事实上,SystemUI正是通过WindowManager来添加自己的主要的UI视图。在SystemUI中,将以下几个部分,分别放置在不同的Window中,然后通过WindowManager将其添加到屏幕上。
注意:除了上面三个常见的window以外,还有其他的window,如GlobalActionDialog(负责显示——关机,重启的弹框),VolumeDialog(显示音量调节),这些window将会在我们介绍各个子主题的时候,依次登场
可能会有读者疑惑:锁屏呢?不在一个单独window下面吗?事实上,锁屏不在一个单独的window中,其本身就是NotificationShade(通知卷帘,即下拉状态栏)。换句话说,锁屏的内容和下拉状态栏的内容在同一个window中。
这里一定要注意的一点是:对应的中文命名方式,从此刻开始,将在对应的类后面用小括号标记其中文命名,之所以有这样的标记,是因为英文名字有很多相似之处,可能不利于记忆,如:StatusBar虽然叫做状态栏,但是在不同的Window中有不同的StatusBar(状态栏),比如,在锁屏状态下有KeyguardViewStatusBar(锁屏状态栏);同样地,在NotificationShade(通知卷帘,即下拉状态栏)中本应该显示StatusBar(状态栏)的地方,并不叫StatusBar。而为了便于记忆,将其中文名字放在小括号中
有了上面的直观感受,我们将结合SystemUI的layout文件,大体的介绍其UI布局及其相关概念,有了这些概念,才能够在纷繁复杂的源码中,找到对应的实现实体。
本文主题是SystemUI的布局介绍,但依然可能会存在这样的疑问:这些布局是什么时候被加载的?他们的加载流程是怎样的?
在后面的文章中,我们会依次介绍这些布局的加载,以及怎么处理各种事件,但再此之前,需要明白他们之间的设计模式和命名规则。
SystemUI各个小模块采用:MVC,MVP等设计模式,因此在他们的命名体系中,经常以xxxView,xxxController,xxxPresenter来代表。例如,代表StatusBar(状态栏)的StatusBarWindowView(状态栏窗口视图),StatusBarWindowController(状态栏窗口控制器),而与StatusBarWindow相关的model即为StatusBar(状态栏)
当然,SystemUI编辑历史长,中间有几次大的修改,并不是所有的类,都遵循这些规则,不过读者在阅读源码的时候,可以按照这些思路进行思考。
而本文以介绍SystemUI布局为主题,而不关心这些布局的加载时刻和流程,但我们会用一段小提示来,简略的概括这些布局的加载流程。在阅读这些简略加载流程的时候,会出现很多相似的名字,因此可以按照上面的命名规则来对应相应的实体
读者可以忽略这些加载流程,因为在后续的文章中,将会详细的介绍这些UI的加载流程和用户操作
我们先从最复杂的NotificationShade(通知卷帘,下拉状态栏)介绍起。
按照上文的说明,会先简略介绍,这个布局的加载流程,如下:
注意:对于上面的调用过程,读者可以完全不用在意,因为后面的文章,将会详细介绍
接下来分析,super_notification_shade.xml
super_notification_shade.xml的源文件如下:
注意:为了减轻阅读障碍,我会省略掉一些非常常见的代码,如一些属性的定义和赋值,省略一些非常简单的布局视图
<com.android.systemui.statusbar.phone.NotificationShadeWindowView>
<com.android.systemui.statusbar.BackDropView>
com.android.systemui.statusbar.BackDropView>
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind" />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_notifications"/>
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim" />
<include layout="@layout/status_bar_expanded"/>
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_in_front" />
<FrameLayout>
<com.android.keyguard.KeyguardMessageArea
android:id="@+id/keyguard_message_area"/>
FrameLayout>
<com.android.systemui.biometrics.AuthRippleView/>
com.android.systemui.statusbar.phone.NotificationShadeWindowView>
从上面的代码结构看,非常滴简单,我们依次介绍如下:
NotificationShadeWindowView:通知卷帘窗口,即下拉状态栏窗口的最顶层视图
BackDropView:幕布视图,它会显示最最底部的一些背景,如播放音乐的时候,显示音乐的专辑图片
ScrimView:总共有三个,他们主要负责遮罩,如在锁屏状态下,整个锁屏界面,稍微变暗淡一些,即通过设置遮罩的透明度来实现
LightRevealScrim:也是遮罩相关的视图,它负责处理一些渐变的动画,如在设置中打开息屏显示,那么在点击电源按钮的时候,将会显示对应的动画,如下图
在这里插入图片描述
status_bar_expanded.xml:即展开的状态中的布局,这里面详细列出了下拉状态栏之后,应该显示哪些内容
brightness_mirror_container.xml:这个是调节亮度条对应的镜像视图,因为还有一个亮度调节条他在status_bar_expanded中,之所以有这个,是为了在调节亮度的时候,隐藏status_bar_expanded中的内容
KeyguardMessageArea:锁屏信息显示区域,如输入pin错误,则会在此处显示wrong pin
除了第七点,可以直接给出参考图以外,其他的都无法给出特别好的参考图,因此可以使用文末推荐的布局查看工具,进行查看
在上面中出现了两个include的xml文件,如果每个文件都介绍,将会消耗大量的篇幅,同时又有些详略不得当,因此,针对一些简单的layout文件,将会一笔带过,如上面的brightness_mirror_container.xml文件。而这些略过的文件,在某些子主题下,将会详细介绍,如上面的brightness_mirror_container.xml将会在调节音量的文章中,详细介绍
因此接下来将会介绍status_bar_expanded.xml文件
status_bar_expanded.xml文件原文如下:
注意:这里依然会省略掉一些属性定义和赋值
<com.android.systemui.statusbar.phone.NotificationPanelView
android:id="@+id/notification_panel">
<FrameLayout
android:id="@+id/big_clock_container" />
<ViewStub
android:id="@+id/keyguard_qs_user_switch_stub"/>
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
<ViewStub
android:id="@+id/keyguard_user_switcher_stub" />
<include layout="@layout/status_bar_expanded_plugin_frame"/>
<include layout="@layout/dock_info_bottom_area_overlay" />
<com.android.keyguard.LockIconView
android:id="@+id/lock_icon_view">
com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:id="@+id/notification_container_parent">
<include layout="@layout/keyguard_status_view"/>
<include layout="@layout/dock_info_overlay" />
<FrameLayout android:id="@+id/qs_frame"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/qs_edge_guideline"/>
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"/>
<include layout="@layout/ambient_indication"
android:id="@+id/ambient_indication_container" />
<include layout="@layout/photo_preview_overlay" />
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
<Button
android:id="@+id/report_rejected_touch"/>
<com.android.systemui.statusbar.phone.TapAgainView
android:id="@+id/shade_falsing_tap_again"/>
com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
<FrameLayout
android:id="@+id/preview_container">
FrameLayout>
com.android.systemui.statusbar.phone.NotificationPanelView>
对上面文件的介绍如下:
注意:对于上面某些xml文件的说明为:空的,一方面,部分内容已经不再使用,另一方面,部分内容将会在后续文章中出现。
在上面的xml文件中,细心的读者可能已经注意到,即锁屏也是在NotificationShade窗口中的。为了能够直观的观察上面的内容,我们使用图片进行说明,如下图:
在上面的源码中,出现了
剩下的keyguard_bottom_area.xml,keyguard_status_view.xml和keyguard_status_bar.xml三个文件,简单明了,能够非常容易的将UI对应上,因此不在此处介绍。这并不影响本文的阅读和理解。不过不用担心,在后续的文章中,我们还会介绍这三个文件,看看他们的交互和初始化过程,此处为了整体行文的紧凑,将其略去
虽然我们已经能够将锁屏相关的UI和代码建立一个联系,但是我们现在还不能将下拉设置中的UI和代码建立联系。接下来,我们处理这部分内容。
在上面的代码中,已经介绍了id为qs_frame的FrameLayout将用来显示快速设置,接下来看看他们是如何布局的。
注意:NotificationStackScrollLayout的布局细节将会在后文中详解
在上文我们知道,id为qs_frame的FrameLayout并没有使用include来加载一个layout,因为我们需要先简单说明一下,其加载过程,然后在看其布局结构
qs_panel.xml原文如下:
<com.android.systemui.qs.QSContainerImpl
android:id="@+id/quick_settings_container" >
<com.android.systemui.qs.NonInterceptingScrollView
android:id="@+id/expanded_qs_scroll_view">
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel">
<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 android:id="@+id/qs_detail" layout="@layout/qs_detail" />
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
com.android.systemui.qs.QSContainerImpl>
为了能够方便的对应起来,见下面的图片
在上面的图片中我们可以看到,QSPanel内部,将会有有各种各样的快速设置的小图标,而这些图标被称为QSTile.
我们将会在QSPanel的源码分析中,详细讲解他们的加载过程。本文旨在介绍SystemUI的布局设计,而非对某个UI的具体细节。
介绍完了qs_frame的布局以后,我们还需要查看该window下面的最后一个布局NotificationStackScrollLayout
NotificationStackScrollLayout是一个类,它也没有使用include来引用其他的布局文件,而其内部则是通过其他的方式来添加布局的。
接下来我们看看这个文件的内容
status_bar_notification_row.xml文件的原文如下:
<com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
android:id="@+id/expandableNotificationRow">
com.android.systemui.statusbar.notification.row.ExpandableNotificationRow>
只有一项内容,当然其内部的内容,我们现在不需要关系,ExpandableNotificationRow是我们观察的最小布局,因为其内部已经涉及具体的内容该怎么显示了,现在我们只需要领略大概的布局,并明白相关的概念
注意:对于通知的排序,优先级等,我们将在后面的文章中,一一介绍,此处只需了解整体布局和基本概念即可
至此,NotificationShade窗口的布局介绍基本已经完成,在这里面我们熟悉了各个UI的实体,当然也省略了一些UI的实体,
省略的部分,将会在后面文章的子主题中,详细介绍。
接下来我们接续查看剩下的两个window——StatusBar和NavigationBar
先简单介绍其加载流程
接下来看看StatusBar(状态栏)的布局文件
super_status_bar.xml的源文件如下:
<com.android.systemui.statusbar.phone.StatusBarWindowView>
<FrameLayout
android:id="@+id/status_bar_launch_animation_container"/>
<FrameLayout
android:id="@+id/status_bar_container" />
com.android.systemui.statusbar.phone.StatusBarWindowView>
非常,非常的清爽!!
接下来我们查看其详细细节
上面id为status_bar_contianer是一个FrameLayout,我们来看看其如何加载布局的。先简单介绍其布局加载流程
status_bar.xml文件原文如下:
<com.android.systemui.statusbar.phone.PhoneStatusBarView
android:id="@+id/status_bar">
<ImageView
android:id="@+id/notification_lights_out"/>
<LinearLayout android:id="@+id/status_bar_contents">
<FrameLayout>
<include layout="@layout/heads_up_status_bar_layout" />
<LinearLayout
android:id="@+id/status_bar_left_side">
<ViewStub
android:id="@+id/operator_name"/>
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"/>
<include layout="@layout/ongoing_call_chip" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"/>
LinearLayout>
FrameLayout>
<android.widget.Space
android:id="@+id/cutout_space_view" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/centered_icon_area"/>
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area">
<include layout="@layout/system_icons" />
com.android.keyguard.AlphaOptimizedLinearLayout>
LinearLayout>
<ViewStub
android:id="@+id/emergency_cryptkeeper_text"/>
com.android.systemui.statusbar.phone.PhoneStatusBarView>
从上面的布局文件可以看到,StatusBar(状态栏)大致分成了三个区域,左,中,右,现在只需要了解其中大体布局即可,即能够在阅读源码的时候,知道源码中的View指的是哪一部分区域即可。在后面的文章中,我们将详细介绍这些布局的内容和实现细节。
至此,我们了解了,SystemUI中,最重要的两个Window的布局。剩下最后一个Window即NavigationBar,因为其布局的简单,我们将在分析Android导航手势的时候,一并介绍
本文,只需要掌握如下几个概念即可,因为后面的系列文章,还会反复提及:
因为最近身体欠佳,加之已经开学,学业繁重,行文仓促,难免有些错误,忘看到的读者给予提醒,谢谢先。
读者可使用下面的工具,进行相应的布局查看
至于Android Studio自带的Layout Inspector就不推荐使用了。