企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
#### 1. 状态栏窗口的创建 在7.1.2节所引用的BaseStatusBar.start()方法的代码中调用了createAndAddWindows()方法进行状态栏窗口的创建。很显然,createAndAddWindow()由PhoneStatusBar或TabletStatusBar实现。以PhoneStatusBar为例,参考其代码: **PhoneStatusBar.java-->PhoneStatusBar.createAndAddWindow()** ``` public void createAndAddWindows() { addStatusBarWindow(); // 直接调用addStatusBarWindow()方法 } ``` 在addStatusBarWindow()方法中,PhoneStatusBar将会构建状态栏的控件树并通过WindowManager的接口为其创建窗口。 **PhoneStatusBar.java-->PhoneStatusBar.addStatusBarWindow()** ``` private void addStatusBarWindow() { // **① 通过getStatusBarHeight()方法获取状态栏的高度** finalint height = getStatusBarHeight(); // **② 为状态栏创建WindowManager.LayoutParams** finalWindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, // 状态栏的宽度为充满整个屏幕宽度 height, // 高度来自于getStatusBarHeight()方法 WindowManager.LayoutParams.TYPE_STATUS_BAR, // 窗口类型 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 状态栏不接受按键事件 /* FLAG_TOUCHABLE_WHEN_WAKING这一标记将使得状态栏接受导致设备唤醒的触摸 事件。通常这一事件会在interceptMotionBeforeQueueing()的过程中被用于 唤醒设备(或从变暗状态下恢复),而InputDispatcher会阻止这一事件发送给 窗口。*/ | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING // FLAG_SPLIT_TOUCH允许状态栏支持触摸事件序列的拆分 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); // 状态栏的Surface像素格式为支持透明度 // 启用硬件加速 lp.flags|= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; //StatusBar的gravity是LEFT和FILL_HORIZONTAL lp.gravity = getStatusBarGravity(); lp.setTitle("StatusBar"); lp.packageName = mContext.getPackageName(); // **③ 创建状态栏的控件树** makeStatusBarView(); // **④ 通过WindowManager.addView()创建状态栏的窗口** mWindowManager.addView(mStatusBarWindow, lp); } ``` 此方法提供了很多重要的信息。 首先是状态栏的高度,由getStatusBarHeight()从资源com.android.internal.R.dimen.status\_bar\_height中获得。这一资源定义在frameworks\\base\\core\\res\\res\\values\\dimens.xml中,默认为25dip。此资源同样在PhoneWindowManager中被用来计算作为布局准绳的八个矩形。 然后是状态栏窗口的LayoutParams的创建。LayoutParams描述了状态栏是怎样的一个窗口。TYPE\_STATUS\_BAR使得PhoneWindowManager为状态栏的窗口分配了较大的layer值,使其可以显示在其他应用窗口之上。FLAG\_NOT\_FOCUSABLE、FLAG\_TOUCHABLE\_WHEN\_WAKING和FLAG\_SPLIT\_TOUCH则定义了状态栏对输入事件的响应行为。 注意 通过创建窗口所使用的LayoutParams来推断一个窗口的行为十分重要。在分析一个需要创建窗口的模块的工作原理时,从窗口创建过程往往是一个不错的切入点。 另外需要知道的是,窗口创建之后,其LayoutParams是会发生变化的。以状态栏为例,创建窗口时其高度为25dip,flags描述其不可接收按键事件。不过当用户按下状态栏导致卷帘下拉时,PhoneStatusBar会通过WindowManager.updateViewLayout()方法修改窗口的LayoutParams的高度为MATCH\_PARENT,即充满整个屏幕以使得卷帘可以满屏显示,并且移除FLAG\_NOT\_FOCUSABLE,使得PhoneStatusBar可以通过监听BACK键以收回卷帘。 在makeStatusBarView()完成控件树的创建之后,WindowManager.addView()将根据控件树创建出状态栏的窗口。显而易见,状态栏控件树的根控件被保存在mStatusBarWindow成员中。 createStatusBarView()负责从R.layout.super\_status\_bar所描述的布局中实例化出一棵控件树。并从这个控件树中取出一些比较重要的控件并保存在对应的成员变量中。因此从R.layout.super\_status\_bar入手可以很容易地得知状态栏的控件树的结构: #### 2.状态栏控件树的结构 参考SystemUI下super\_status\_bar.xml所描述的布局内容,可以看到其根控件是一个名为StatusBarWindowView的控件,它继承自FrameLayout。在其下的两个直接子控件如下: - @layout/status\_bar所描述的布局。这是用户平时所见的状态栏。 - PenelHolder:这个继承自FrameLayout的控件是状态栏的卷帘。在其下的两个直接子控件@layout/status\_bar\_expanded以及@layout/quick\_settings分别对应于卷帘之中的通知列表面板以及快速设定面板。 在正常情况下,StatusBarWindowView中只有@layout/status\_bar所描述的布局是可见的,并且状态栏窗口为com.android.internal.R.dimen.status\_bar\_height所定义的高度。当StatusBarWindowView截获了ACTION\_DOWN的触摸事件后,会修改窗口的高度为MATCH\_PARENT,然后将PenelHolder设为可见并跟随用户的触摸轨迹,由此实现了卷帘的下拉效果。 说明 PenelHolder集成自FrameLayout。那么它如何做到在@layout/status\_bar\_expanded以及@layout/quick\_settings两个控件之间进行切换显示呢?答案就在第6章所介绍的ViewGroup. getChildDrawingOrder()方法中。此方法的返回值影响了子控件的绘制顺序,同时也影响了控件接收触摸事件的优先级。当PenelHolder希望显示@layout/status\_bar\_expanded面版时,它在此方法中将此面版的绘制顺序放在最后,使其在绘制时能够覆盖@layout/quick\_settings,并且优先接受触摸事件。反之则将@layout/quick\_settings的绘制顺序放在最后即可。 因此状态栏控件树的第一层结构如图7-2所示。 :-: ![](http://img.blog.csdn.net/20150814134219928?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图 7 - 2状态栏控件树的结构1 再看status\_bar.xml所描述的布局内容,其根控件是一个继承自FrameLayout的名为StatusBarView类型的控件,makeStatusBarView()方法会将其保存为mStatusBarView。其直接子控件有三个: - @id/notification\_lights\_out,一个ImageView,并且一般情况下它是不可见的。在SystemUIVisiblity中有一个名为SYSTEM\_UI\_FLAG\_LOW\_PROFILE的标记。当一个应用程序希望让用户的注意力更多地集中在它所显示的内容时,可以在其SystemUIVisibility中添加这一标记。SYSTEM\_UI\_FLAG\_LOW\_PROFILE会使得状态栏与导航栏进入低辨识度模式。低辨识度模式下的状态栏将不会显示任何信息,只是在黑色背景中显示一个灰色圆点而已。而这一个黑色圆点即是这里的id/notification\_lights\_out。 - @id/status\_bar\_contents,一个LinearLayout,状态栏上各种信息的显示场所。 - @id/ticker,一个LinearLayout,其中包含了一个ImageSwitcher和一个TickerView。在正常情况下@id/ticker是不可见的。当一个新的通知到来时(例如一条新的短信),状态栏上会以动画方式逐行显示通知的内容,使得用户可以在无需下拉卷帘的情况下了解新通知的内容。这一功能在状态栏中被称之为Ticker。而@id/ticker则是完成Ticker功能的场所。makeStatusBarView()会将@id/ticker保存为mTickerView。 至此,状态栏控件树的结构可以扩充为图7-3所示。 :-: ![](http://img.blog.csdn.net/20150814134232090?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图 7 - 3状态栏控件树的结构2 再来分析@id/status\_bar\_contents所包含的内容。如前文所述,状态栏所显示的信息共有5种,因此@id/status\_bar\_contents中的子控件分别用来显示这5种信息。其中通知信息显示在@id/notification\_icon\_area里,而其他四种信息则显示在@id/system\_icon\_area之中。 - @id/notification\_icon\_area,一个LinearLayout。包含了两个子控件分别是类型为StatusBarIconView的@id/moreIcon以及一个类型为IconMerger的@id/notificationIcons。IconMerger继承自LinearLayout。通知信息的图标都会以一个StatusBarIconView的形式存储在IconMerger之中。而IconMeger和LinearLayout的区别在于,如果它在onLayout()的过程中发现会其内部所容纳的StatusBarIconView的总宽度超过了它自身的宽度,则会设置@id/moreIcon为可见,使得用户得知有部分通知图标因为显示空间不够而被隐藏。makeStausBarView()会将@id/notificationIcons保存为成员变量mNotificationIcons。因此当新的通知到来时,只要将一个StatusBarIconView放置到mNotificationIcons即可显示此通知的图标了。 - @id/system\_icon\_area,也是一个LinearLayout。它容纳了除通知信息的图标以外的四种信息的显示。在其中有负责显示时间信息的@id/clock,负责显示电量信息的@id/battery,负责信号信息显示的@id/signal\_cluster以及负责容纳系统状态区图标的一个LinearLayout——@id/statusIcons。其中@id/statusIcons会被保存到成员变量mStatusIcons中,当需要显示某一个系统状态图标时,将图标放置到mStatusIcons中即可。 注意 @id/system\_icon\_area的宽度定义为WRAP\_CONTENT,而@id/notification\_icon\_area的weight被设置为1。在这种情况下,@id/system\_icon\_area将在状态栏右侧根据其所显示的图标个数调整其尺寸。而@id/notification\_icon\_area则会占用状态栏左侧的剩余空间。这说明了一个问题:系统图标区将优先占用状态栏的空间进行信息的显示。这也是IconMerger类以及@id/moreIcon存在的原因。 于是可以将图7-3扩展为图7-4。 :-: ![](http://img.blog.csdn.net/20150814134243958?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图 7 - 4状态栏控件树的结构3 另外,在@layout/status\_bar\_expanded之中有一个类型为NotificationRowLayout的控件@id/latestItems,并且会被makeStatusBarView()保存到mPile成员变量中。它位于下拉卷帘中,是通知信息列表的容器。 在分析控件树结构的过程中发现了如下几个重要的控件: - mStatusBarWindow,整个状态栏的根控件。它包含了两棵子控件树,分别是常态下的状态栏以及下拉卷帘。 - mStatusBarView,常态下的状态栏。它所包含的三棵子控件树分别对应了状态栏的三种工作状态——低辨识度模式、Ticker以及常态。这三棵控件树会随着这三种工作状态的切换交替显示。 - mNotificationIcons,继承自LinearLayout的IconMerger控件的实例,负责容纳通知图标。当mNotificationIcons的宽度不足以容纳所有通知图标时,会将@id/moreIcon设置为可见以告知用户存在未显示的通知图标。 - mTickerView,实现了当新通知到来时的动画效果,使得用户可以在无需下拉卷帘的情况下了解新通知的内容。 - mStatusIcons,一个LinearLayout,它是系统状态图标区,负责容纳系统状态图标。 - mPile,一个NotificationRowLayout,它作为通知列表的容器被保存在下拉卷帘中。因此当一个通知信息除了需要将其图标添加到mNotificationIcons以外,还需要将其详细信息(标题、描述等)添加到mPile中,使得用户在下来卷帘中可以看到它。 对状态栏控件树的结构分析至此便告一段落了。接下来将从通知信息以及系统状态图标两个方面介绍状态栏的工作原理。希望读者能够理解本节所介绍的几个重要控件所在的位置以及其基本功能,这将使得后续内容的学习更加轻松。