- 这里讲的是下拉状态栏看到的东西,包括statusBar,quick setting,以及下边的通知。
- 当然了,上边的statusBar还有一种完全展开的状态,下边还显示一个屏幕亮度控制的控件。
- quick setting面板也可以完全展开。
- 这里主要学习下通知面板相关的内容
下边是完全拉开的状态,通知栏不见了,quick setting面板完全可见
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面板底部那个用户头像的点击效果一样。
<!-- 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
锁屏界面那个钟表的界面
<!-- 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
锁屏页面的状态栏
<!-- 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,不过都需要在设置里配置的,默认是没有的。
比如提示文字hello,默认是空的,可以在settings里,自己输入自己想显示的内容,还可以选择锁屏界面是否显示通知,
看布局有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的区别是,这种点击那个头像,不是弹个对话框,而是展开列表,效果见图
<!-- 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,具体显示哪个,还是都不显示,研究下代码里咋处理的。
首先设置里要开启多用户,如下图
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
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.有锁屏界面的
- 锁屏界面的高度不做处理
// 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文字也可以点击,跳转到通知的设置页面
>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.图片
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创建的,都是固定样式
<!-- 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图片
长按上边的通知,就能看到下图的效果,这个显示的内容都放在notification_guts里,布局参考补充2
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,然后研究了下通知栏列表的创建过程,以及点击事件等。