android13#SystemUI#04下拉状态栏

5,546 阅读25分钟
  • 这里讲的是下拉状态栏看到的东西,包括statusBar,quick setting,以及下边的通知。
  • 当然了,上边的statusBar还有一种完全展开的状态,下边还显示一个屏幕亮度控制的控件。
  • quick setting面板也可以完全展开。
  • 这里主要学习下通知面板相关的内容 image.png

下边是完全拉开的状态,通知栏不见了,quick setting面板完全可见

image.png

1.NotificationShadeWindowView

这个控件就是下拉通知栏看到的根容器了,分析下如何添加到窗口

1.1.inflateStatusBarWindow

CentralSurfacesImpl.java

    private void inflateStatusBarWindow() {
    //...
        ## 注解获取,通过inflate加载布局,返回根布局控件
        mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
        ## 这个controller是通过注解实例化的,构造方法里包含上边的view
        mNotificationShadeWindowViewController = mCentralSurfacesComponent.getNotificationShadeWindowViewController();
        ## 把mNotificationShadeWindowView赋值给controller
     mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
    ## 就是方法名字,处理expanded status bar        
        mNotificationShadeWindowViewController.setupExpandedStatusBar();
    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
        makeStatusBarView(result);
        mNotificationShadeWindowController.attach();//作用是addView,参考补充2

        mStatusBarWindowController.attach();
    }

>1.通过注解实例化

StatusBarViewModule.java

    @Provides
    @CentralSurfacesComponent.CentralSurfacesScope
    public static NotificationShadeWindowView providesNotificationShadeWindowView(
            LayoutInflater layoutInflater) {
        NotificationShadeWindowView notificationShadeWindowView = (NotificationShadeWindowView)
                layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null);//补充3
//...
        return notificationShadeWindowView;
    }

>2.attach

NotificationShadeWindowControllerImpl.java 的attach方法,通过windowManager添加的,

    public void attach() {
//...
        mWindowManager.addView(mNotificationShadeView, mLp);
        //...
    }
    @Override
    public void setNotificationShadeView(ViewGroup view) {
        mNotificationShadeView = view;
    }        

>3.super_notification_shade.xml

下边看下相关的布局,简单了解下层级结构

<!-- This is the notification shade window. -->
#FrameLayout
<com.android.systemui.shade.NotificationShadeWindowView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.android.systemui.statusbar.BackDropView
        android:id="@+id/backdrop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        sysui:ignoreRightInset="true"
    >
        <ImageView android:id="@+id/backdrop_back"
                   android:layout_width="match_parent"
                   android:scaleType="centerCrop"
                   android:layout_height="match_parent" />
        <ImageView android:id="@+id/backdrop_front"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent"
                   android:scaleType="centerCrop"
                   android:visibility="invisible" />
    </com.android.systemui.statusbar.BackDropView>

    <com.android.systemui.scrim.ScrimView
        android:id="@+id/scrim_behind"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
    />

    <com.android.systemui.scrim.ScrimView
        android:id="@+id/scrim_notifications"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
    />

    <com.android.systemui.statusbar.LightRevealScrim
        android:id="@+id/light_reveal_scrim"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
## 展开的状态栏,参考1.2
    <include layout="@layout/status_bar_expanded"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />

## FrameLayout
    <include layout="@layout/brightness_mirror_container" />

    <com.android.systemui.scrim.ScrimView
        android:id="@+id/scrim_in_front"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
    />

    <!-- Keyguard messages -->
    <LinearLayout
        android:id="@+id/keyguard_message_area_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_marginTop="@dimen/status_bar_height"
        android:layout_gravity="top|center_horizontal"
        android:gravity="center_horizontal">
        ##textview
        <com.android.keyguard.AuthKeyguardMessageArea
            android:id="@+id/keyguard_message_area"
            style="@style/Keyguard.TextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/keyguard_lock_padding"
            android:gravity="center"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:focusable="true" />
    </LinearLayout>

    <FrameLayout android:id="@+id/keyguard_bouncer_container"
        android:paddingTop="@dimen/status_bar_height"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:background="@android:color/transparent"
        android:visibility="invisible"
        android:clipChildren="false"
        android:clipToPadding="false" />

    <com.android.systemui.biometrics.AuthRippleView
        android:id="@+id/auth_ripple"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
    />
</com.android.systemui.shade.NotificationShadeWindowView>

1.2.status_bar_expanded.xml

<!--小节4,自定义的帧布局-->
<com.android.systemui.shade.NotificationPanelView
    android:id="@+id/notification_panel"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">
# 用户切换头像,可点击,显示与否有条件,具体见后边分析
    <ViewStub
        android:id="@+id/keyguard_qs_user_switch_stub"
        android:layout="@layout/keyguard_qs_user_switch"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />
## FrameLayout ,match/wrap
    <include layout="@layout/status_bar_expanded_plugin_frame"/>
## ConstraintLayout,横竖屏的时候里边的部分view会动态修改约束
    <com.android.systemui.shade.NotificationsQuickSettingsContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="@integer/notification_panel_layout_gravity"
        android:id="@+id/notification_container_parent"
        android:clipToPadding="false"
        android:clipChildren="false">

        <include
            layout="@layout/keyguard_status_view"
            android:visibility="gone"/>
## 空的
        <include layout="@layout/dock_info_overlay"/>

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

        <!--这个用来加载下拉看到的状态栏的,就是时间日期那个玩意,This view should be after qs_frame so touches are dispatched first to it. That gives
             it a chance to capture clicks before the NonInterceptingScrollView disallows all
             intercepts -->
        <ViewStub
            android:id="@+id/qs_header_stub"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
        />
    <!--中心线,横屏的时候会用到,其他控件可以以它为约束显示在左边或者右边-->
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/qs_edge_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            systemui:layout_constraintGuide_percent="0.5"
            android:orientation="vertical"/>

        <!-- This layout should always include a version of
             NotificationStackScrollLayout, as it is expected from
             NotificationPanelViewController. -->
             # 通知相关的都在这个容器里,这是个自定义的ViewGroup,布局里就一个容器
        <include layout="@layout/notification_stack_scroll_layout" />
## 空
        <include layout="@layout/photo_preview_overlay" />

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

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

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

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

    <include layout="@layout/dock_info_bottom_area_overlay" />
<!--小锁或者指纹图标-->
    <com.android.keyguard.LockIconView
        android:id="@+id/lock_icon_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <!-- Background protection -->
        <ImageView
            android:id="@+id/lock_icon_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/fingerprint_bg"
            android:visibility="invisible"/>

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

    </com.android.keyguard.LockIconView>

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

>1.keyguard_qs_user_switch.xml

这个显示是有条件的,具体逻辑后边有分析,下图贴出了显示的效果,那个头像点击以后就会弹出一个对话框,可以切换用户,效果和qs面板底部那个用户头像的点击效果一样。

image.png

<!-- This is a view that shows a user switcher in Keyguard. -->
<FrameLayout
    android:id="@+id/keyguard_qs_user_switch_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="end">
    <!-- We add a background behind the UserAvatarView with the same color and with a circular shape
         so that this view can be expanded into a Dialog or an Activity. -->
    <FrameLayout
        android:id="@+id/kg_multi_user_avatar_with_background"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|end"
        android:layout_marginEnd="16dp"
        android:background="@drawable/keyguard_framed_avatar_background">
        <com.android.systemui.statusbar.phone.UserAvatarView
            android:id="@+id/kg_multi_user_avatar"
            android:layout_width="@dimen/kg_framed_avatar_size"
            android:layout_height="@dimen/kg_framed_avatar_size"
            systemui:avatarPadding="0dp"
            systemui:badgeDiameter="18dp"
            systemui:badgeMargin="1dp"
            systemui:frameColor="@color/kg_user_avatar_frame"
            systemui:framePadding="0dp"
            systemui:frameWidth="0dp">
        </com.android.systemui.statusbar.phone.UserAvatarView>
    </FrameLayout>
</FrameLayout>

>2.keyguard_status_view

锁屏界面那个钟表的界面

image.png

<!-- This is a view that shows general status information in Keyguard. -->
<com.android.keyguard.KeyguardStatusView
    android:id="@+id/keyguard_status_view"
    android:orientation="vertical"
    systemui:layout_constraintStart_toStartOf="parent"
    systemui:layout_constraintEnd_toEndOf="parent"
    systemui:layout_constraintTop_toTopOf="parent"
    android:layout_marginHorizontal="@dimen/status_view_margin_horizontal"
    android:clipChildren="false"
    android:layout_width="0dp"
    android:layout_height="wrap_content">
    <LinearLayout
        android:id="@+id/status_view_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:orientation="vertical">
        <include
            layout="@layout/keyguard_clock_switch"
            android:id="@+id/keyguard_clock_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <FrameLayout
            android:id="@+id/status_view_media_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="@dimen/qs_media_padding"
            />
    </LinearLayout>
</com.android.keyguard.KeyguardStatusView>

>3.keyguard_clock_switch.xml

锁屏界面的时钟,有两个,一个大的一个小的

<!-- This is a view that shows clock information in Keyguard. -->
<com.android.keyguard.KeyguardClockSwitch
    android:id="@+id/keyguard_clock_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal|top">
    <FrameLayout
        android:id="@+id/lockscreen_clock_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:paddingStart="@dimen/clock_padding_start">
        <com.android.keyguard.AnimatableClockView
            android:id="@+id/animatable_clock_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:gravity="start"
            android:textSize="@dimen/clock_text_size"
            android:fontFamily="@font/clock"
            android:elegantTextHeight="false"
            android:singleLine="true"
            android:fontFeatureSettings="pnum"
            chargeAnimationDelay="350"
            dozeWeight="200"
            lockScreenWeight="400"
        />
    </FrameLayout>
    <FrameLayout
        android:id="@+id/lockscreen_clock_view_large"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/keyguard_slice_view"
        android:visibility="gone">
        <com.android.keyguard.AnimatableClockView
            android:id="@+id/animatable_clock_view_large"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
			android:singleLine="true"
            android:layout_gravity="center"
            android:gravity="center_horizontal"
            android:textSize="@dimen/large_clock_text_size"
            android:fontFamily="@font/clock"
            android:typeface="monospace"
            android:elegantTextHeight="false"
            chargeAnimationDelay="200"
            dozeWeight="200"
            lockScreenWeight="400"
        />
    </FrameLayout>

    <!-- Not quite optimal but needed to translate these items as a group. The
         NotificationIconContainer has its own logic for translation. -->
    <LinearLayout
        android:id="@+id/keyguard_status_area"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_below="@id/lockscreen_clock_view">

      <include layout="@layout/keyguard_slice_view"
               android:id="@+id/keyguard_slice_view"
               android:layout_width="match_parent"
               android:layout_height="wrap_content" />

      <com.android.systemui.statusbar.phone.NotificationIconContainer
          android:id="@+id/left_aligned_notification_icon_container"
          android:layout_width="match_parent"
          android:layout_height="@dimen/notification_shelf_height"
          android:paddingStart="@dimen/below_clock_padding_start_icons"
          android:visibility="invisible"
          />
    </LinearLayout>
</com.android.keyguard.KeyguardClockSwitch>

>4.keyguard_status_bar

锁屏页面的状态栏

image.png

<!-- Extends RelativeLayout -->
<com.android.systemui.statusbar.phone.KeyguardStatusBarView
    android:id="@+id/keyguard_header"
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_header_height_keyguard"
    android:baselineAligned="false"
    android:gravity="center_vertical"
    >
# 图上右侧部分
    <LinearLayout
        android:id="@+id/status_icon_area"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:layout_alignParentEnd="true"
        android:gravity="center_vertical|end" >

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

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

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

    <Space
        android:id="@+id/cutout_space_view"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:gravity="center"
        android:visibility="gone" />
# 这个好像是左侧的文字提示,比如没有sim卡,手机没信号啥的
    <com.android.keyguard.CarrierText
        android:id="@+id/keyguard_carrier_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:layout_marginStart="@dimen/keyguard_carrier_text_margin"
        android:layout_toStartOf="@id/system_icons_container"
        android:gravity="center_vertical"
        android:ellipsize="marquee"
        android:textDirection="locale"
        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
        android:textColor="?attr/wallpaperTextColorSecondary"
        android:singleLine="true"
        systemui:showMissingSim="true"
        systemui:showAirplaneMode="true" />

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

>5.keyguard_bottom_area

这个是锁屏页面,底部展示的内容,比如提示文字,左右2侧可以展示2个图标快速启动某些app,不过都需要在设置里配置的,默认是没有的。

image.png

比如提示文字hello,默认是空的,可以在settings里,自己输入自己想显示的内容,还可以选择锁屏界面是否显示通知,

image.png

看布局有2行文字,一行是我们在设置里的自己定义的,还有一行是系统提示,比如swipe up to open(这个一般点击一下屏幕就会提示的)

## FrameLayout
<com.android.systemui.statusbar.phone.KeyguardBottomAreaView
    android:id="@+id/keyguard_bottom_area"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:outlineProvider="none" > <!-- Put it above the status bar header -->

    <LinearLayout
        android:id="@+id/keyguard_indication_area"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
        android:layout_gravity="bottom|center_horizontal"
        android:orientation="vertical">

        <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
            android:id="@+id/keyguard_indication_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:paddingStart="@dimen/keyguard_indication_text_padding"
            android:paddingEnd="@dimen/keyguard_indication_text_padding"
            android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
            android:accessibilityLiveRegion="polite"/>
            
        <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
            android:id="@+id/keyguard_indication_text_bottom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:minHeight="48dp"
            android:layout_gravity="center_horizontal"
            android:layout_centerHorizontal="true"
            android:paddingStart="@dimen/keyguard_indication_text_padding"
            android:paddingEnd="@dimen/keyguard_indication_text_padding"
            android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
            android:maxLines="2"
            android:ellipsize="end"
            android:alpha=".8"
            android:accessibilityLiveRegion="polite"
            android:visibility="gone"/>

    </LinearLayout>

    <com.android.systemui.common.ui.view.LaunchableImageView
        android:id="@+id/start_button"
        android:layout_height="@dimen/keyguard_affordance_fixed_height"
        android:layout_width="@dimen/keyguard_affordance_fixed_width"
        android:layout_gravity="bottom|start"
        android:scaleType="center"
        android:tint="?android:attr/textColorPrimary"
        android:background="@drawable/keyguard_bottom_affordance_bg"
        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
        android:visibility="gone" />

    <com.android.systemui.common.ui.view.LaunchableImageView
        android:id="@+id/end_button"
        android:layout_height="@dimen/keyguard_affordance_fixed_height"
        android:layout_width="@dimen/keyguard_affordance_fixed_width"
        android:layout_gravity="bottom|end"
        android:scaleType="center"
        android:tint="?android:attr/textColorPrimary"
        android:background="@drawable/keyguard_bottom_affordance_bg"
        android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
        android:visibility="gone" />

    <FrameLayout
        android:id="@+id/overlay_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
##空的
        <include layout="@layout/keyguard_bottom_area_overlay" />
    </FrameLayout>
#空的
    <include layout="@layout/ambient_indication"
             android:id="@+id/ambient_indication_container" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>

>6.keyguard_user_switcher

下图就是显示的位置,这种和上边那个keyguard_qs_user_switcher的区别是,这种点击那个头像,不是弹个对话框,而是展开列表,效果见图 image.png

image.png

<!-- This is a view that shows a user switcher in Keyguard. -->
<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView ##FrameLayout
    android:id="@+id/keyguard_user_switcher_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="end">

    <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView ##线性布局
        android:id="@+id/keyguard_user_switcher_list"
        android:orientation="vertical"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_gravity="top|end"
        android:gravity="end" />

</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>

>7.user_switcher

上边布局里有2种user_switcher,具体显示哪个,还是都不显示,研究下代码里咋处理的。

首先设置里要开启多用户,如下图

image.png NotificationPanelViewController.java

## 这个方法是在构造方法末尾调用的
    void onFinishInflate() {
        loadDimens();
        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);

        FrameLayout userAvatarContainer = null;
        KeyguardUserSwitcherView keyguardUserSwitcherView = null;

# 根据配置里的boolean值,决定加载哪个布局还是都不加载
        if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled(
                mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))) {
            if (mKeyguardQsUserSwitchEnabled) {
                ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
                userAvatarContainer = (FrameLayout) stub.inflate();
            } else {
                ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
                keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
            }
        }

        mKeyguardStatusBarViewController =
                mKeyguardStatusBarViewComponentFactory.build(
                                mKeyguardStatusBar,
                                mNotificationPanelViewStateProvider)
                        .getKeyguardStatusBarViewController();
        mKeyguardStatusBarViewController.init();

        mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
        # 2种view,里边对应两种Controller来加载头像。
        updateViewControllers(
                mView.findViewById(R.id.keyguard_status_view),
                userAvatarContainer, #先判断这个不为null就使用这个,if
                keyguardUserSwitcherView); #再判断这个是否为null,else if

配置读取:
qs_show_user_switcher_for_single_user 【sw600dp 是true,默认的是false】 config_keyguardUserSwitcher【values-sw600dp下是true,values下是false】

    private void updateUserSwitcherFlags() {
        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
                com.android.internal.R.bool.config_keyguardUserSwitcher);
        mKeyguardQsUserSwitchEnabled =
                mKeyguardUserSwitcherEnabled
                        && mFeatureFlags.isEnabled(Flags.QS_USER_DETAIL_SHORTCUT);
    }

还有一个reInflateViews方法用到,showQsUserSwitch为true,显示的是qs_user_switch那个布局,showKeyguardUserSwitcher为true,显示的是user_switch那个布局。

    void reInflateViews() {
    //...
        // Re-inflate the keyguard user switcher group.
        updateUserSwitcherFlags();
        boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled(
                mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user));
        boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
        boolean showKeyguardUserSwitcher =
                !mKeyguardQsUserSwitchEnabled
                        && mKeyguardUserSwitcherEnabled
                        && isUserSwitcherEnabled;
        FrameLayout userAvatarView = (FrameLayout) reInflateStub(
                R.id.keyguard_qs_user_switch_view /* viewId */,
                R.id.keyguard_qs_user_switch_stub /* stubId */,
                R.layout.keyguard_qs_user_switch /* layoutId */,
                showQsUserSwitch /* enabled */); // 测试效果可以直接把这里改为true或false
        KeyguardUserSwitcherView keyguardUserSwitcherView =
                (KeyguardUserSwitcherView) reInflateStub(
                        R.id.keyguard_user_switcher_view /* viewId */,
                        R.id.keyguard_user_switcher_stub /* stubId */,
                        R.layout.keyguard_user_switcher /* layoutId */,
                        showKeyguardUserSwitcher /* enabled */); // 测试效果可以直接把这里改为true或false

        updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
                keyguardUserSwitcherView);    

Flags.QS_USER_DETAIL_SHORTCUT 的值

    <!-- Whether the multi-user icon on the lockscreen opens the QS user detail. If false, clicking
         the multi-user icon will display a list of users directly on the lockscreen. The multi-user
         icon is only shown if config_keyguardUserSwitcher=false in the frameworks config. -->
    <bool name="flag_lockscreen_qs_user_detail_shortcut">false</bool>

1.3.NotificationsQuickSettingsContainer

下拉通知栏的核心内容都在这个布局里了,小节1.2的简化

## ConstraintLayout
    <com.android.systemui.shade.NotificationsQuickSettingsContainer>
        <include
            layout="@layout/keyguard_status_view"
            android:visibility="gone"/>
#quick setting 内容,另一篇介绍
        <FrameLayout
            android:id="@+id/qs_frame"
            android:layout="@layout/qs_panel"
        />

        <ViewStub
            android:id="@+id/qs_header_stub"
        />

             # 通知相关的都在这个容器里,这是个自定义的ViewGroup
        <include layout="@layout/notification_stack_scroll_layout" />

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

    </com.android.systemui.shade.NotificationsQuickSettingsContainer>

>1.NotificationStackScrollLayout

自定义的容器,通知相关的都在这里,就是开头第一张图,下边那块白色的部分。

/**
 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
 */
public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {

1.4.TouchEvent

触摸事件优先交给小节2.1里的handler处理,没有处理的话自己处理。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //分发事件前先自己处理,见2.2小节
        Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
        //自己没有处理才交给下层
        result = result != null ? result : super.dispatchTouchEvent(ev);

        mInteractionEventHandler.dispatchTouchEventComplete();

        return result;
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
    
        boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
        if (!intercept) {
        //交给自己下层处理
            intercept = super.onInterceptTouchEvent(ev);
        }
        if (intercept) {
            mInteractionEventHandler.didIntercept(ev);
        }

        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //先自己处理
        boolean handled = mInteractionEventHandler.handleTouchEvent(ev);

        if (!handled) {
        //自己没处理,交给下层处理
            handled = super.onTouchEvent(ev);
        }

        if (!handled) {
            mInteractionEventHandler.didNotHandleTouchEvent(ev);
        }

        return handled;
    }

1.5.InteractionEventHandler

触摸交互事件的接口,具体实现在2.2

    interface InteractionEventHandler {
        /**
         * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
         * to the super method.
         */
        Boolean handleDispatchTouchEvent(MotionEvent ev);

        /**
         * Called after all dispatching is done.
         */

        void dispatchTouchEventComplete();

        /**
         * Returns if the view should intercept the touch event.
         *
         * The touch event may still be interecepted if
         * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
         */
        boolean shouldInterceptTouchEvent(MotionEvent ev);

        /**
         * Called when the view decides to intercept the touch event.
         */
        void didIntercept(MotionEvent ev);

        boolean handleTouchEvent(MotionEvent ev);

        void didNotHandleTouchEvent(MotionEvent ev);

        boolean interceptMediaKey(KeyEvent event);

        boolean dispatchKeyEvent(KeyEvent event);

        boolean dispatchKeyEventPreIme(KeyEvent event);
    }

1.6.getLargeScreenShadeHeaderBarView

注解加载的id为qs_header_stub的ViewStub

    @Provides
    @Named(LARGE_SCREEN_SHADE_HEADER)
    @CentralSurfacesComponent.CentralSurfacesScope
    public static View getLargeScreenShadeHeaderBarView(
            NotificationShadeWindowView notificationShadeWindowView,
            FeatureFlags featureFlags) {
        ViewStub stub = notificationShadeWindowView.findViewById(R.id.qs_header_stub);
        int layoutId = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
                ? R.layout.combined_qs_header
                : R.layout.large_screen_shade_header;
        stub.setLayoutResource(layoutId);
        View v = stub.inflate();
        return v;
    }

2.NotificationShadeWindowViewController.java

这个类核心方法是setupExpandedStatusBar(),处理了NotificationShadeWindowView的touch事件,listener方法太多,这里就贴下截图
mView就是NotificationShadeWindowView

image.png

2.1.setupExpandedStatusBar

下边这行是设置下拉的的手势操作

        mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {//见2.2详细分析
        //...

    //dragHelper在上边的InteractionEventHandler里用到
     setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper());

2.2.InteractionEventHandler

>1.shouldInterceptTouchEvent

是否拦截触摸事件,一层层判断

            public boolean shouldInterceptTouchEvent(MotionEvent ev) {
                if (mStatusBarStateController.isDozing() && !mService.isPulsing()
                        && !mDockManager.isDocked()) {
                    // Capture all touch events in always-on.
                    return true;
                }

                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                    // capture all touches if the alt auth bouncer is showing
                    return true;
                }
                //先判断锁屏icon
                if (mLockIconViewController.onInterceptTouchEvent(ev)) {
                    // immediately return true; don't send the touch to the drag down helper
                    return true;
                }

                boolean intercept = false;
                //这里就是锁屏界面了,交给mDragDownHelper处理
                if (mNotificationPanelViewController.isFullyExpanded()
                        && mDragDownHelper.isDragDownEnabled()
                        && !mService.isBouncerShowing()
                        && !mStatusBarStateController.isDozing()) {
                    intercept = mDragDownHelper.onInterceptTouchEvent(ev);
                }

                return intercept;

            }

>2.didIntercept

上边的intercept返回true,那么执行这个,发送一个cancel的事件

            public void didIntercept(MotionEvent ev) {
                MotionEvent cancellation = MotionEvent.obtain(ev);
                cancellation.setAction(MotionEvent.ACTION_CANCEL);
                //这个是通知栏
                mStackScrollLayout.onInterceptTouchEvent(cancellation);
                //这个是整个下拉的状态栏,包括qs
                mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation);
                cancellation.recycle();
            }

>3.handleTouchEvent

            public boolean handleTouchEvent(MotionEvent ev) {
                boolean handled = false;
                if (mStatusBarStateController.isDozing()) {
                    handled = !mService.isPulsing();
                }

                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                    // eat the touch
                    handled = true;
                }

                if ((mDragDownHelper.isDragDownEnabled() && !handled)
                        || mDragDownHelper.isDraggingDown()) {
                    //交给mDragDownHelper处理
                    handled = mDragDownHelper.onTouchEvent(ev);
                }

                return handled;
            }

>4.didNotHandleTouchEvent

没人处理touch事件

            public void didNotHandleTouchEvent(MotionEvent ev) {
                final int action = ev.getActionMasked();
                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                    mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
                }
            }

>5.handleDispatchTouchEvent

这个是直接不处理,也就是shouldInterceptTouchEvent返回false以后,往下分发事件,里边会先调用这个方法,见1.4小节调用。没人处理的话返回null

            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                if (mStatusBarViewController == null) { // Fix for b/192490822
                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
                    return false;
                }
                boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
                boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
                boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;

                if(isDown && mNotificationPanelViewController.shouldIgnorTouch(ev)){
            //这个是自己加的拦截事件。
                    return true;
                }
                boolean expandingBelowNotch = mExpandingBelowNotch;
                if (isUp || isCancel) {
                    mExpandingBelowNotch = false;
                }

                if (!isCancel && mService.shouldIgnoreTouch()) {
                    return false;
                }

                if (isDown) {
                    mTouchActive = true;
                    mTouchCancelled = false;
                } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
                        || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                    mTouchActive = false;
                }
                if (mTouchCancelled || mExpandAnimationRunning) {
                    return false;
                }

                if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
                    //正在进行解锁动画,取消触摸事件
                    cancelCurrentTouch();
                    return true;
                }

                mFalsingCollector.onTouchEvent(ev);
                mPulsingWakeupGestureHandler.onTouchEvent(ev);
                mStatusBarKeyguardViewManager.onTouch(ev);
                if (mBrightnessMirror != null
                        && mBrightnessMirror.getVisibility() == View.VISIBLE) {
                    // 亮度调节条正在显示
                    if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
                        return false;
                    }
                }
                if (isDown) {
                //点击的是通知列表意外的地方,关闭展开的通知栏。
                    mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
                }

                if (mStatusBarStateController.isDozing()) {
                    mService.extendDozePulse();
                }
                //交给锁屏icon处理
                mLockIconViewController.onTouchEvent(
                        ev,
                        () -> mService.wakeUpIfDozing(
                                SystemClock.uptimeMillis(),
                                mView,
                                "LOCK_ICON_TOUCH",
                                PowerManager.WAKE_REASON_GESTURE)
                );

                // In case we start outside of the view bounds (below the status bar), we need to
                // dispatch
                // the touch manually as the view system can't accommodate for touches outside of
                // the
                // regular view bounds.
                //这个没看懂,mView不是全屏的吗?这个触摸还是跑到屏幕外边?
                if (isDown && ev.getY() >= mView.getBottom()) {
                    mExpandingBelowNotch = true;
                    expandingBelowNotch = true;
                }
                if (expandingBelowNotch) {
                    return mStatusBarViewController.sendTouchToView(ev);
                }

                if (!mIsTrackingBarGesture && isDown
                        && mNotificationPanelViewController.isFullyCollapsed()) {
                    float x = ev.getRawX();
                    float y = ev.getRawY();
                    if (mStatusBarViewController.touchIsWithinView(x, y)) {
                        if (mStatusBarWindowStateController.windowIsShowing()) {
                            mIsTrackingBarGesture = true;
                            return mStatusBarViewController.sendTouchToView(ev);
                        } else { // it's hidden or hiding, don't send to notification shade.
                            return true;
                        }
                    }
                } else if (mIsTrackingBarGesture) {
                    final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev);
                    if (isUp || isCancel) {
                        mIsTrackingBarGesture = false;
                    }
                    return sendToStatusBar;
                }

                return null;
            }

3.NotificationPanelView

status_bar_expanded.xml的根容器

public final class NotificationPanelView extends FrameLayout {

3.1.touchEvent

    public boolean onInterceptTouchEvent(MotionEvent event) {
    //调用的是4.4里的方法
        return mTouchHandler.onInterceptTouchEvent(event);
    }

    @Override
    public void dispatchConfigurationChanged(Configuration newConfig) {
        super.dispatchConfigurationChanged(newConfig);
        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
    }

//handler见 小节4.4
    public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) {
        super.setOnTouchListener(touchHandler);
        mTouchHandler = touchHandler;
    }

4.NotificationPanelViewController

4.1.构造方法

构造方法里有70来个参数,简单看下

    @Inject
    public NotificationPanelViewController(NotificationPanelView view,
    //...
        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
            @Override
            public void onKeyguardFadingAwayChanged() {
                updateExpandedHeightToMaxHeight();
            }
        });
//...
        mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener());
        //设置触摸事件,handler见4.4
        mView.setOnTouchListener(createTouchHandler());
        mView.setOnConfigurationChangedListener(config -> loadDimens());
//.
        onFinishInflate();//参考4.2

4.2.onFinishInflate

下边这个方法是在构造方法末尾调用的

   void onFinishInflate() {
        loadDimens();
        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);

//...
// 锁屏界面状态栏控制器
        mKeyguardStatusBarViewController =
                mKeyguardStatusBarViewComponentFactory.build(
                                mKeyguardStatusBar,
                                mNotificationPanelViewStateProvider)
                        .getKeyguardStatusBarViewController();
        mKeyguardStatusBarViewController.init();

        mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
        //锁屏界面用户切换的显示逻辑
        updateViewControllers(
                mView.findViewById(R.id.keyguard_status_view),
                userAvatarContainer,
                keyguardUserSwitcherView);

        NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                R.id.notification_stack_scroller);
         //attach方法里是对stackScrollLayout的设置以及初始化
        mNotificationStackScrollLayoutController.attach(stackScrollLayout);
         // 这个就是管理下拉通知栏那部分的,这里设置各种监听
        mNotificationStackScrollLayoutController.setOnHeightChangedListener(
                new NsslHeightChangedListener());
        mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                mOnOverscrollTopChangedListener);
        mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
        mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
        mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                mOnEmptySpaceClickListener);
        addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
        //底部区域
        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
//底部view处理的代码,kotlin写的,非常复杂,后边再研究
        initBottomArea();

        mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
        mQsFrame = mView.findViewById(R.id.qs_frame);
        mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController);
//...
    }

4.3.禁掉下拉状态栏

本来想着在touch事件里处理,感觉有点麻烦,后来想想这玩意下拉的时候是一点点显示的,或者是有个动画来显示,反正肯定有个中间过程动态处理这个控件的高度的,找了一下,如下.

>1.非锁屏界面

可以直接利用StatusBarManager处理

    StatusBarManager statusBarManager = getContext().getSystemService(StatusBarManager.class);
        //先获取当前的disable信息
            Pair<Integer, Integer> pair = statusBarManager.getDisableInfo().toFlags();
            int flag1 = pair.first;
            int flag2 = pair.second;
            if (mConfig.isHideQSPanel()) {
            //这个是隐藏QS面板
                flag2 |= statusBarManager.DISABLE2_QUICK_SETTINGS;
            } else {
            //这个是取消隐藏QS面板
                flag2 &= ~statusBarManager.DISABLE2_QUICK_SETTINGS;
            }
            if (mConfig.isHideStatusBar()) {
            //这个就无法下拉了
                flag2 |= statusBarManager.DISABLE2_NOTIFICATION_SHADE;
            } else {
            //取消设置
                flag2 &= ~statusBarManager.DISABLE2_NOTIFICATION_SHADE;
            }
            statusBarManager.disable2(flag2);            

> 2.没有锁屏界面的简单处理办法

把原本的参数h随便改个名字(比如h2),不用这个值,然后弄个局部变量h为0,那么这个控件就永远显示不出来了。这种有个问题,锁屏界面也都啥也看不见,只有个背景,像时钟,提示文字啥的都没了。

// NotificationPanelViewController.java
    private void setExpandedHeightInternal(float h2) {
        final float h=0;

>3.有锁屏界面的

  1. 锁屏界面的高度不做处理
// NotificationPanelViewController.java
    private void setExpandedHeightInternal(float h2) {
        final float h;
        if(getKeyguardShowing()||isOnKeyguard()){
            h=h2;//锁屏界面不好直接隐藏整个容器,因为还有时钟,锁屏icon等在这个容器里  
        }else{
            h=0;//非锁屏界面,直接等于0就完事了,反正整个容器都不需要可见
        }

2. 禁掉锁屏界面的drag手势

LockscreenShadeTransitionController.kt

  • 锁屏界面状态栏以下区域确实下拉没反应,可从状态栏那里往下拉是可以的。
    //这个类就是锁屏界面的下拉手势
    class DragDownHelper(
    //...
        val isDragDownEnabled: Boolean//这个改成false
            get() = false

需要再加上如下代码,

//NotificationShadeWindowViewController.java

        mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
            @Override
            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                if (mStatusBarViewController == null) { // Fix for b/192490822
                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
                    return false;
                }
                boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
                boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
                boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;

                if(isDown && ev.getY()<150){
                    //如果触摸点是状态栏里,那么就当已经处理过触摸事件了。
                    return true;
                }

4.4.TouchHandler

NotificationPanelView的触摸事件,在构造方法里设置的,

    public final class TouchHandler implements View.OnTouchListener {
        private long mLastTouchDownTime = -1L;

        /** 是否中断触摸事件,3.1用到 */
        public boolean onInterceptTouchEvent(MotionEvent event) {
        //...

        //触摸事件处理
        public boolean onTouch(View v, MotionEvent event) {
        //...
        

5.NotificationStackScrollLayoutController

5.1.attach

如下方法给mView赋值,查找下这个方法哪里调用的

    public void attach(NotificationStackScrollLayout view) {
        mView = view;

>1.onFinishInflate

4.2方法里会调用attach方法,如下,这个方法是其构造方法里调用的

    void onFinishInflate() {
//...
        NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                R.id.notification_stack_scroller);
                //这里调用了attach方法赋值
        mNotificationStackScrollLayoutController.attach(stackScrollLayout);

>2.CentralSurfacesImpl.java

  • 流程整理 CentralSurfacesImpl.java里方法调用顺序:
  • start >> createAndAddWindows >> makeStatusBarView >> inflateStatusBarWindow
    private void inflateStatusBarWindow() {
//.
//实例化小节4对象,构造方法4.1末尾调用4.2的方法也就是补充1的方法
        mNotificationPanelViewController =
                mCentralSurfacesComponent.getNotificationPanelViewController();
//...
        mStackScrollerController =
                mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
      //这里直接就getView了
        mStackScroller = mStackScrollerController.getView();

5.2.NotificationStackScrollLayout.java

这个是个自定义的ViewGroup,下拉看到的通知面板内容都在这个容器里

public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {

>1.onFinishInflate

        protected void onFinishInflate() {
            super.onFinishInflate();
            inflateEmptyShadeView();//补充3
            inflateFooterView();//补充2
        }

>2.inflateFooterView

        protected void inflateFooterView() {
        //直接把footer的布局加载进来
            FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
                    R.layout.status_bar_notification_footer, this, false);
                    //这里设置了clear按钮的功能
            footerView.setClearAllButtonClickListener(v -> {
                if (mFooterClearAllListener != null) {
                    mFooterClearAllListener.onClearAll();
                }
                clearNotifications(ROWS_ALL, true /* closeShade */);
                footerView.setSecondaryVisible(false /* visible */, true /* animate */);
            });
            setFooterView(footerView);
        } 
    void setFooterView(@NonNull FooterView footerView) {
        int index = -1;
        if (mFooterView != null) {
        //如果有旧的,移除旧的
            index = indexOfChild(mFooterView);
            removeView(mFooterView);
        }
        mFooterView = footerView;
        addView(mFooterView, index);//加入容器里
        if (mManageButtonClickListener != null) {
        //manager按钮的点击事件,具体的参考5.3
            mFooterView.setManageButtonClickListener(mManageButtonClickListener);
        }
    }

>3.inflateEmptyShadeView

  • 没有通知的时候显示这个,布局默认是gone的
        private void inflateEmptyShadeView() {
            EmptyShadeView oldView = mEmptyShadeView;
            EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
                    R.layout.status_bar_no_notifications, this, false);
            view.setOnClickListener(v -> {
                final boolean showHistory = mController.isHistoryEnabled();
                Intent intent = showHistory
                        ? new Intent(Settings.ACTION_NOTIFICATION_HISTORY)
                        : new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
                mCentralSurfaces.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
            });
            setEmptyShadeView(view);
            updateEmptyShadeView(
                    oldView == null ? R.string.empty_shade_text : oldView.getTextResource(),
                    oldView == null ? 0 : oldView.getFooterTextResource(),
                    oldView == null ? 0 : oldView.getFooterIconResource());
        }

>4.reinflateViews

        void reinflateViews() {
            inflateFooterView();
            inflateEmptyShadeView();
            updateFooter();
            mSectionsManager.reinflateViews();//5.4.2
        }

>5.onLayout

child到底咋layout的,暂时没看懂,代码太多

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //先把所有的child都放置在上边
        float centerX = getWidth() / 2.0f;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            //
            float width = child.getMeasuredWidth();
            float height = child.getMeasuredHeight();
            child.layout((int) (centerX - width / 2.0f),
                    0,
                    (int) (centerX + width / 2.0f),
                    (int) height);
        }
        setMaxLayoutHeight(getHeight());
        updateContentHeight();
        clampScrollPosition();
        requestChildrenUpdate();
        updateFirstAndLastBackgroundViews();
        updateAlgorithmLayoutMinHeight();
        updateOwnTranslationZ();

        mAnimateStackYForContentHeightChange = false;
    }

>6.updateContentHeight

    private void updateContentHeight() {
        final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
        final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
        final float height =
                (int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
                        /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
                        shelfIntrinsicHeight);
        mIntrinsicContentHeight = height;

        mContentHeight = (int) (height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding);
        updateScrollability();
        clampScrollPosition();
        updateStackPosition();
        mAmbientState.setContentHeight(mContentHeight);
    }

5.3.底部按钮点击事件

    // attach方法
            mView.setFooterClearAllListener(() ->
                    //...
            mView.setManageButtonClickListener(v -> {
                if (mNotificationActivityStarter != null) {
                //manager按钮的点击事件,补充1
                    mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
                }
            });
//..silent按钮的点击事件
            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());//补充2

>1.startHistoryIntent

StatusBarNotificationActivityStarter

    public void startHistoryIntent(View view, boolean showHistory) {
        boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
        ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
            @Override
            public boolean onDismiss() {
                AsyncTask.execute(() -> {
                    Intent intent = showHistory ? new Intent(
                            Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
                            Settings.ACTION_NOTIFICATION_SETTINGS);
                    TaskStackBuilder tsb = TaskStackBuilder.create(mContext)
                            .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
                    if (showHistory) {
                        tsb.addNextIntent(intent);
                    }

                    ActivityLaunchAnimator.Controller viewController =
                            ActivityLaunchAnimator.Controller.fromView(view,
                                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
                            );
                    ActivityLaunchAnimator.Controller animationController =
                            viewController == null ? null
                                : new StatusBarLaunchAnimatorController(viewController,
                                        mCentralSurfaces,
                                    true /* isActivityIntent */);

                    mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
                            intent.getPackage(),
                            (adapter) -> tsb.startActivities(
                                    getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
                                    UserHandle.CURRENT));
                });
                return true;
            }

            @Override
            public boolean willRunAnimationOnKeyguard() {
                return animate;
            }
        };
        //先隐藏keyguard,再执行上边的action
        mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null,
                false /* afterKeyguardGone */);
    }

>2.clearSilentNotifications

    public void clearSilentNotifications() {
        // Leave the shade open if there will be other notifs left over to clear
        final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
        mView.clearNotifications(ROWS_GENTLE, closeShade);
    }

5.4.SectionHeaderController.kt

  • 有的通知是可以删除的,如下图的叉号,至于左侧的silent文字也可以点击,跳转到通知的设置页面 image.png

>1.构造方法

    @SectionHeaderScope
    internal class SectionHeaderNodeControllerImpl @Inject constructor(
        @NodeLabel override val nodeLabel: String,
        private val layoutInflater: LayoutInflater,
        @HeaderText @StringRes private val headerTextResId: Int, //左侧按钮的文字
        private val activityStarter: ActivityStarter,
        @HeaderClickAction private val clickIntentAction: String //左侧按钮的action
    ) : NodeController, SectionHeaderController {

        private var _view: SectionHeaderView? = null
        private var clearAllButtonEnabled = false
        //右侧叉号的点击事件
        private var clearAllClickListener: View.OnClickListener? = null
        //左侧按钮的点击事件
        private val onHeaderClickListener = View.OnClickListener {
            activityStarter.startActivity(
                    Intent(clickIntentAction),
                    true /* onlyProvisioned */,
                    true /* dismissShade */,
                    Intent.FLAG_ACTIVITY_SINGLE_TOP)
        }

>2.reinflateView

方法的调用参考5.5

        override fun reinflateView(parent: ViewGroup) {
    //..没有直接添加到容器,至于哪里加的,暂时没找到
            val inflated = layoutInflater.inflate(
                    R.layout.status_bar_notification_section_header,
                    parent,
                    false /* attachToRoot */)
                    as SectionHeaderView
            inflated.setHeaderText(headerTextResId)
            inflated.setOnHeaderClickListener(onHeaderClickListener)
            clearAllClickListener?.let { inflated.setOnClearAllClickListener(it) }
            if (oldPos != -1) {
                parent.addView(inflated, oldPos)
            }
            _view = inflated
            _view?.setClearSectionButtonEnabled(clearAllButtonEnabled)
        }

5.5.NotificationSectionsManager.kt

看下上边reinflateView方法的调用, 下边一堆一样的方法,用的都是上边SectionHeaderNodeControllerImpl的实例,注解生成的,对应不同的注解

>1.构造方法

    class NotificationSectionsManager @Inject internal constructor(
    //...下边这几个就是通过注解不同获取不同的对象的,参考补充3
        @IncomingHeader private val incomingHeaderController: SectionHeaderController,
        @PeopleHeader private val peopleHeaderController: SectionHeaderController,
        @AlertingHeader private val alertingHeaderController: SectionHeaderController,
        @SilentHeader private val silentHeaderController: SectionHeaderController,

>2.reinflateViews

初始化的时候调用,配置改变的时候调用,以及5.2.4调用

        fun reinflateViews() {
            silentHeaderController.reinflateView(parent)//5.4.2
            alertingHeaderController.reinflateView(parent)
            peopleHeaderController.reinflateView(parent)
            incomingHeaderController.reinflateView(parent)
            mediaContainerController.reinflateView(parent)
            keyguardMediaController.attachSinglePaneContainer(mediaControlsView)
        }

>3.NotificationSectionHeadersModule.kt

补充1构造方法里的实例都在这个module里,dagger2的注解语法,不会的需要先研究下,要不可能看不懂咋初始化的

    @Provides
    @SilentHeader
    @SysUISingleton
    @JvmStatic fun providesSilentHeaderSubcomponent(
        builder: Provider<SectionHeaderControllerSubcomponent.Builder>
    ) = builder.get()
            .nodeLabel("silent header")
            .headerText(R.string.notification_section_header_gentle)
            .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
            .build()

    @Provides
    @SilentHeader
    @JvmStatic fun providesSilentHeaderNodeController(
        @SilentHeader subcomponent: SectionHeaderControllerSubcomponent
    ) = subcomponent.nodeController

    @Provides
    @SilentHeader
    @JvmStatic fun providesSilentHeaderController(
        @SilentHeader subcomponent: SectionHeaderControllerSubcomponent
    ) = subcomponent.headerController

6.ExpandableNotificationRowController

6.1.init

    public void init(NotificationEntry entry) {
        mActivatableNotificationViewController.init();
        mView.initialize(
//.

            if (mAllowLongPress) {
                if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_DRAG_TO_CONTENTS)) {
                    mView.setDragController(mDragController);
                }

                mView.setLongPressListener((v, x, y, item) -> {
                    if (mView.isSummaryWithChildren()) {
                    //下图2 android system 通知下有多个通知,长按是展开
                        mView.expandNotification();
                        return true;
                    }//下图1,长按就是显示alarm的通知设置了。
                    return mNotificationGutsManager.openGuts(v, x, y, item);
                });
            }

>2.图片

image.png

image.png

7.NotificationsController

7.1.注解生成

这里主要讲解下Notification row的创建以及其点击事件

    @SysUISingleton
    @Provides
    static NotificationsController provideNotificationsController(
            Context context,
            Provider<NotificationsControllerImpl> realController,
            Provider<NotificationsControllerStub> stubController) {
        if (context.getResources().getBoolean(R.bool.config_renderNotifications)) {//配置里是true
            return realController.get();
        } else {
            return stubController.get();
        }
    }

7.2.init

@SysUISingleton
class NotificationsControllerImpl @Inject constructor(
    private val notificationListener: NotificationListener,
    private val commonNotifCollection: Lazy<CommonNotifCollection>,
    private val notifPipeline: Lazy<NotifPipeline>,
    private val notifLiveDataStore: NotifLiveDataStore,
    private val targetSdkResolver: TargetSdkResolver,
    private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
    private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
    private val notificationLogger: NotificationLogger,
    private val notificationRowBinder: NotificationRowBinderImpl,
    private val notificationsMediaManager: NotificationMediaManager,
    private val headsUpViewBinder: HeadsUpViewBinder,
    private val clickerBuilder: NotificationClicker.Builder,
    private val animatedImageNotificationManager: AnimatedImageNotificationManager,
    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
    private val bubblesOptional: Optional<Bubbles>,
    private val fgsNotifListener: ForegroundServiceNotificationListener,
    private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
    private val featureFlags: FeatureFlags
) : NotificationsController {

    override fun initialize(
        centralSurfaces: CentralSurfaces,
        presenter: NotificationPresenter,
        listContainer: NotificationListContainer,
        stackController: NotifStackController,
        notificationActivityStarter: NotificationActivityStarter,
        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
    ) {
        notificationListener.registerAsSystemService()

        notifPipeline.get().addCollectionListener(object : NotifCollectionListener {
            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                listContainer.cleanUpViewStateForEntry(entry)
            }
        })
//设置通知的点击事件处理器
        notificationRowBinder.setNotificationClicker(
                clickerBuilder.build(
                    Optional.ofNullable(centralSurfaces), bubblesOptional,
                        notificationActivityStarter))
        //设置依赖
        notificationRowBinder.setUpWithPresenter(
                presenter,
                listContainer,
                bindRowCallback)
        headsUpViewBinder.setPresenter(presenter)
        notifBindPipelineInitializer.initialize()
        animatedImageNotificationManager.bind()

        notifPipelineInitializer.get().initialize(
                notificationListener,
                notificationRowBinder,
                listContainer,
                stackController)

        targetSdkResolver.initialize(notifPipeline.get())
        notificationsMediaManager.setUpWithPresenter(presenter)
        notificationLogger.setUpWithContainer(listContainer)
        peopleSpaceWidgetManager.attach(notificationListener)
        fgsNotifListener.init()
        if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
            memoryMonitor.get().init()
        }
    }

8.NotificationRowBinderImpl

  • 就是管理通知列表的

8.1.inflateView

  • 根据通知数据entry,生成或者更新view,调用的地方参考8.2
        public void inflateViews(
                NotificationEntry entry,
                @NonNull NotifInflater.Params params,
                NotificationRowContentBinder.InflationCallback callback)
                throws InflationException {
                //这里的group就是NotificationStackScrollLayout.java
            ViewGroup parent = mListContainer.getViewParentForNotification(entry);

            if (entry.rowExists()) {
                mIconManager.updateIcons(entry);
                ExpandableNotificationRow row = entry.getRow();
                row.reset();
                updateRow(entry, row);//补充2
                inflateContentViews(entry, params, row, callback);//补充3
            } else {
                mIconManager.createIcons(entry);
                //看下布局如何加载的,8.6.1
                mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
                        row -> {
                            // Setup the controller for the view.
                            ExpandableNotificationRowComponent component =
                                    mExpandableNotificationRowComponentBuilder
                                            .expandableNotificationRow(row)
                                            .notificationEntry(entry)
                                            .onExpandClickListener(mPresenter)
                                            .listContainer(mListContainer)
                                            .build();
                            ExpandableNotificationRowController rowController =
                                    component.getExpandableNotificationRowController();
                            rowController.init(entry);
                            entry.setRowController(rowController);
                            bindRow(entry, row);//补充1
                            updateRow(entry, row);//补充2
                            inflateContentViews(entry, params, row, callback);
                        });
            }
        }

>1.bindRow

    private void bindRow(NotificationEntry entry, ExpandableNotificationRow row) {
        mListContainer.bindRow(row);
        mNotificationRemoteInputManager.bindRow(row);
        row.setOnActivatedListener(mPresenter);
        entry.setRow(row);
        mNotifBindPipeline.manageRow(entry, row);
        mBindRowCallback.onBindRow(row);
    }

>2.updateRow

给view注册点击事件

    private void updateRow(
            NotificationEntry entry,
            ExpandableNotificationRow row) {
        //参考8.7.1
        requireNonNull(mNotificationClicker).register(row, entry.getSbn());
    }

>3.inflateContentViews

    private void inflateContentViews(
            NotificationEntry entry,
            NotifInflater.Params inflaterParams,
            ExpandableNotificationRow row,
            @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
        final boolean useIncreasedCollapsedHeight =
                mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
        final boolean isLowPriority;
        if (inflaterParams != null) {
            // NEW pipeline
            isLowPriority = inflaterParams.isLowPriority();
        } else {
            // LEGACY pipeline
            mNotifPipelineFlags.checkLegacyPipelineEnabled();
            isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
        }

        RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
        params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
        params.setUseLowPriority(isLowPriority);

        if (mNotificationLockscreenUserManager.needsRedaction(entry)) {
            params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
        } else {
            params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
        }

        params.rebindAllContentViews();
        mRowContentBindStage.requestRebind(entry, en -> {
            row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
            row.setIsLowPriority(isLowPriority);
            if (inflationCallback != null) {
                inflationCallback.onAsyncInflationFinished(en);
            }
        });
    }

8.2..NotifInflaterImpl.java

    public void rebindViews(@NonNull NotificationEntry entry, @NonNull Params params,
            @NonNull InflationCallback callback) {
        inflateViews(entry, params, callback);
    }

    @Override
    public void inflateViews(@NonNull NotificationEntry entry, @NonNull Params params,
            @NonNull InflationCallback callback) {
        try {
            requireBinder().inflateViews(
                    entry,
                    params,
                    wrapInflationCallback(callback));
        } 
    }

8.3.PreparationCoordinator.java

方法A,B里会调用方法C,完事C里边又会调用D,E.

A 方法在attach方法里被指定给了pipeline的listener

>1.attach

        public void attach(NotifPipeline pipeline) {
            mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
            mAdjustmentProvider.addDirtyListener(
                    () -> mNotifInflatingFilter.invalidateList("adjustmentProviderChanged"));

            pipeline.addCollectionListener(mNotifCollectionListener);
            //在分组|排序之后加载,add方法参考8.4
            pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews);//补充2
            pipeline.addFinalizeFilter(mNotifInflationErrorFilter);
            pipeline.addFinalizeFilter(mNotifInflatingFilter);
        }

>2.inflateAllRequiredViews

    // A
        private void inflateAllRequiredViews(List<ListEntry> entries) {
            for (int i = 0, size = entries.size(); i < size; i++) {
                ListEntry entry = entries.get(i);
                if (entry instanceof GroupEntry) {
                    GroupEntry groupEntry = (GroupEntry) entry;
                    inflateRequiredGroupViews(groupEntry);//补充3
                } else {
                    NotificationEntry notifEntry = (NotificationEntry) entry;
                    inflateRequiredNotifViews(notifEntry);//...
                }
            }
        }

>3.inflateRequiredGroupViews

//B
        private void inflateRequiredGroupViews(GroupEntry groupEntry) {
            NotificationEntry summary = groupEntry.getSummary();
            List<NotificationEntry> children = groupEntry.getChildren();
            inflateRequiredNotifViews(summary);//补充4
            for (int j = 0; j < children.size(); j++) {
                NotificationEntry child = children.get(j);
                boolean childShouldBeBound = j < mChildBindCutoff;
                if (childShouldBeBound) {
                    inflateRequiredNotifViews(child);//补充4
    //..

>4.inflateRequiredNotifViews

    //C
        private void inflateRequiredNotifViews(NotificationEntry entry) {
            NotifUiAdjustment newAdjustment = mAdjustmentProvider.calculateAdjustment(entry);
            if (mInflatingNotifs.contains(entry)) {
                // Already inflating this entry
                String errorIfNoOldAdjustment = "Inflating notification has no adjustments";
                if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) {
                //补充5
                    inflateEntry(entry, newAdjustment, "adjustment changed while inflating");
                }
                return;
            }
            @InflationState int state = mInflationStates.get(entry);
            switch (state) {
                case STATE_UNINFLATED:
                    inflateEntry(entry, newAdjustment, "entryAdded");//补充5
                    break;
                case STATE_INFLATED_INVALID:
                    rebind(entry, newAdjustment, "entryUpdated");
                    break;
                case STATE_INFLATED:
                    String errorIfNoOldAdjustment = "Fully inflated notification has no adjustments";
                    if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) {
                        rebind(entry, newAdjustment, "adjustment changed after inflated");
                    }
                    break;
                case STATE_ERROR:
                    if (needToReinflate(entry, newAdjustment, null)) {
                        inflateEntry(entry, newAdjustment, "adjustment changed after error");
                    }
                    break;
                default:
                    // Nothing to do.
            }
        }

>5.inflateEntry

     //D   
        private void inflateEntry(NotificationEntry entry,
                NotifUiAdjustment newAdjustment,
                String reason) {
    //...
            mNotifInflater.inflateViews(entry, params, this::onInflationFinished);
        }
    //E
        private void rebind(NotificationEntry entry,
                NotifUiAdjustment newAdjustment,
                String reason) {
    //...
            mNotifInflater.rebindViews(entry, params, this::onInflationFinished);
        }    

8.4..NotifPipeline.kt

>1.addOnBeforeFinalizeFilterListener

        fun addOnBeforeFinalizeFilterListener(listener: OnBeforeFinalizeFilterListener) {
            mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener)
        }

8.5.ShadeListBuilder.java

>1.addOnBeforeFinalizeFilterListener

        void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
            mPipelineState.requireState(STATE_IDLE);
            mOnBeforeFinalizeFilterListeners.add(listener);
        }  

>2.addFinalizeFilter

    void addFinalizeFilter(NotifFilter filter) {
        Assert.isMainThread();
        mPipelineState.requireState(STATE_IDLE);

        mNotifFinalizeFilters.add(filter);
        filter.setInvalidationListener(this::onFinalizeFilterInvalidated);
    }

>3.attach

  • mChoreographer就是NotifPipelineChoreographerImpl
        public void attach(NotifCollection collection) {
            Assert.isMainThread();
            collection.addCollectionListener(mInteractionTracker);
            collection.setBuildListener(mReadyForBuildListener);
            mChoreographer.addOnEvalListener(this::buildList);//补充4
        }

>4.buildList

  • 分9步,加载数据用的
private void buildList() {
        //...
            // Step 6: Filter out entries after pre-group filtering, grouping, promoting, and sorting
            // Now filters can see grouping, sectioning, and order information to determine whether
            // to filter or not.
            dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList);    
       //...
           }

>7.dispatchOnBeforeFinalizeFilter

        private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
            for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
                mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
            }
        }   

>8.rebuildListIfBefore

  • 这个方法很多地方调用,调用的地方又都是别人的listener
  • schedule方法会依次调用所有的listener,也就是上边的buildList方法了。
            private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) {
            final @PipelineState.StateName int currentState = mPipelineState.getState();
            if (currentState == STATE_IDLE) {
                scheduleRebuild(/* reentrant = */ false, rebuildState);
                return;
            }

            if (rebuildState > currentState) {
                return;
            }

            scheduleRebuild(/* reentrant = */ true, rebuildState);
        }
        
        private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) {
            if (!reentrant) {
                mConsecutiveReentrantRebuilds = 0;
                mChoreographer.schedule();//这个会执行补充4 buildList方法
                return;
            }
        }    

8.6.RowInflaterTask

>1.inflate

小节8.1调用

        public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
                RowInflationFinishedListener listener) {
            mListener = listener;
            AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
            mEntry = entry;
            entry.setInflationTask(this);
            inflater.inflate(R.layout.status_bar_notification_row, parent, this);//this回调就是补充3
        }

>2.status_bar_notification_row.xml

  • 外层是个自定义的帧布局,里边还有好多层,具体看名字
  • 我在布局上边加了test按钮,如下row,展开和非展开状态
  • NotificationContentView显示通知内容的,至于视图是通过Notification.builder创建的,都是固定样式 image.png
    <!-- extends FrameLayout -->
    <com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
        android:id="@+id/expandableNotificationRow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:clickable="true"
        >
        <!-- Menu displayed behind notification added here programmatically -->

        <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
            android:id="@+id/backgroundNormal"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
            android:id="@+id/backgroundDimmed"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.android.systemui.statusbar.notification.row.NotificationContentView
            android:id="@+id/expanded"
           android:layout_width="match_parent"
           android:layout_height="wrap_content" />

        <com.android.systemui.statusbar.notification.row.NotificationContentView
            android:id="@+id/expandedPublic"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/veto"
            android:layout_width="48dp"
            android:layout_height="0dp"
            android:gravity="end"
            android:layout_marginEnd="-80dp"
            android:background="@null"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            />

        <ViewStub
            android:layout="@layout/notification_children_container"
            android:id="@+id/child_container_stub"
            android:inflatedId="@+id/notification_children_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />

        <ViewStub
            android:layout="@layout/notification_guts"
            android:id="@+id/notification_guts_stub"
            android:inflatedId="@+id/notification_guts"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />

        <com.android.systemui.statusbar.notification.FakeShadowView
            android:id="@+id/fake_shadow"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.android.systemui.statusbar.notification.row.ExpandableNotificationRow>

>3.onInflateFinished

    public void onInflateFinished(View view, int resid, ViewGroup parent) {
        if (!mCancelled) {
            try {
                mEntry.onInflationTaskFinished();//把里边的inflaterTask设为空
                mListener.onInflationFinished((ExpandableNotificationRow) view);//参考8.1
            } 
        }
    }

>4.guts图片

image.png 长按上边的通知,就能看到下图的效果,这个显示的内容都放在notification_guts里,布局参考补充2 image.png

8.7.NotificationClicker

  • Builder注解生成对象,build方法就是new一个这个对象

>1.register

前边notification row里说过给view绑定点击事件,就是这个类里的register方法,如下:
有的通知是有intent的,比如闹铃,点击一般会跳到闹铃的设置页面去了,这时候需要点击事件。
有的通知单纯的就是提示,不需要点击事件的

    public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
        Notification notification = sbn.getNotification();
        if (notification.contentIntent != null || notification.fullScreenIntent != null
                || row.getEntry().isBubble()) {
            row.setOnClickListener(this);
            row.setOnDragSuccessListener(mOnDragSuccessListener);
        } else {
            row.setOnClickListener(null);
            row.setOnDragSuccessListener(null);
        }
    }

>2.onClick

    public void onClick(final View v) {
        if (!(v instanceof ExpandableNotificationRow)) {
            //布局8.6.1,不是不做处理
            return;
        }
//...
        final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
        final NotificationEntry entry = row.getEntry();

        // Check if the notification is displaying the menu, if so slide notification back
        //下边这堆if都不处理intent的,
        if (isMenuVisible(row)) {
            mLogger.logMenuVisible(entry);
            row.animateResetTranslation();
            return;
        } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
            row.getNotificationParent().animateResetTranslation();
            return;
        } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
            // We never want to open the app directly if the user clicks in between
            // the notifications.
            return;
        } else if (row.areGutsExposed()) {
            // ignore click if guts are exposed
            return;
        }

        // 标记一下这个row刚被点击了.
        row.setJustClicked(true);
        DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));

        if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
            mBubblesOptional.get().collapseStack();
        }

//最终intent是这里处理的,8.8.1
        mNotificationActivityStarter.onNotificationClicked(entry, row);
    }

8.8.StatusBarNotificationActivityStarter

class StatusBarNotificationActivityStarter implements NotificationActivityStarter {

>1.onNotificationClicked

    public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {

        if (mRemoteInputManager.isRemoteInputActive(entry)
                && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
            //.
            mRemoteInputManager.closeRemoteInputs();
            return;
        }
        Notification notification = entry.getSbn().getNotification();
        final PendingIntent intent = notification.contentIntent != null
                ? notification.contentIntent
                : notification.fullScreenIntent;
        final boolean isBubble = entry.isBubble();

        //
        if (intent == null && !isBubble) {
            return;
        }

        boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble;
        final boolean willLaunchResolverActivity = //...
        boolean showOverLockscreen =//...
        ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() {
            @Override
            public boolean onDismiss() {
            //具体的intent处理在这里,补充2
                return handleNotificationClickAfterKeyguardDismissed(
                        entry, row, intent, isActivityIntent, animate, showOverLockscreen);
            }

            @Override
            public boolean willRunAnimationOnKeyguard() {
                return animate;
            }
        };
        //锁屏界面先解锁屏幕再执行上边的action,非锁屏界面执行action的onDismiss方法
        if (showOverLockscreen) {
            mIsCollapsingToShowActivityOverLockscreen = true;
            postKeyguardAction.onDismiss();
        } else {
            mActivityStarter.dismissKeyguardThenExecute(
                    postKeyguardAction,
                    null,
                    willLaunchResolverActivity);
        }
    }

>2.handleNotificationClickAfterKeyguardDismissed

    private boolean handleNotificationClickAfterKeyguardDismissed(
            NotificationEntry entry,
            ExpandableNotificationRow row,
            PendingIntent intent,
            boolean isActivityIntent,
            boolean animate,
            boolean showOverLockscreen) {
//核心就是这个了,反正最终都是走这里,补充3
        final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
                entry, row, intent, isActivityIntent, animate);

        if (showOverLockscreen) {
            mShadeController.addPostCollapseAction(runnable);
            mShadeController.collapseShade(true /* animate */);
        } else if (mKeyguardStateController.isShowing()
                && mCentralSurfaces.isOccluded()) {
            mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
            mShadeController.collapseShade();
        } else {
            runnable.run();
        }

        // Always defer the keyguard dismiss when animating.
        return animate || !mNotificationPanel.isFullyCollapsed();
    }

>3.handleNotificationClickAfterPanelCollapsed

        if (canBubble) {
        } else {//补充4
            startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent);
        }

>4.startNotificationIntent

                    (adapter) -> {
//...
//这里就是跳转activity了
                        int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                null, null, options);
                        return result;
                    });

9.总结

  • 简单介绍下展开状态的通知栏的布局结构以及显示ui
  • 主要分三部分,状态栏(展开状态的),quick settings, notifiaction
  • 主要介绍的是通知栏,这个是个自定义的viewgroup,然后研究了下通知栏列表的创建过程,以及点击事件等。