1.简介
这篇主要学习下非展开的状态栏,也就是收缩状态栏,下图红线部分
2.视图加载流程
复习下StatusBarWindowView如何添加
- 首先CentralSurfacesImpl.java的构造方法里实例化了StatusBarWindowController.java,
- 而StatusBarWindowController的构造方法里又实例化了StatusBarWindowView,
- 完事attach方法addView
>1.CentralSurfacesImpl.java
CentralSurfacesImpl里边会调用StatusBarWindowController的attach方法
@Inject
public CentralSurfacesImpl(
//...
StatusBarWindowController statusBarWindowController,//controller
//...
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
mStatusBarWindowController.attach();//见2.1.1添加到窗口
}
2.1.StatusBarWindowController
@Inject
public StatusBarWindowController(
Context context,
//构造方法传递的view
@StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
//...
>1.attach
public void attach() {
//参数见补充2
mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
// 添加view
mWindowManager.addView(mStatusBarWindowView, mLp);
>2.getBarLayoutParams
如下,状态栏高度是固定的
private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
height,
2.2.问题?
需求是隐藏状态栏
-
测试1 :我想着把上边的height改成0就完事了,结果改完测试发现不行。 height为0以后状态栏还在,测试失败。why?
-
测试2 :调用mWindowManager.removeView(mStatusBarWindowView),状态栏确实不见了,可是锁屏再解锁屏幕,程序崩掉了,测试失败。
-
测试3 :调用mStatusBarWindowView.setVisibility(View.GONE); 这个好像可以,不过home页面或者all apps页面,其实可以看到顶部还是有一个状态栏高度的空白的【这个应该是launcher3里边弄的padding】,不过打开一个app的话,发现顶部没有空白,基本ok
2.3.StatusBarWindowView
>1.注解生成的
@Module
abstract class StatusBarWindowModule {
@Module
companion object {
@JvmStatic
@Provides
@SysUISingleton
@InternalWindowView
fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView {
return layoutInflater.inflate(
R.layout.super_status_bar,
/* root= */null
) as StatusBarWindowView?
}
>2.布局super_status_bar
<!-- This is the status bar window. -->
<com.android.systemui.statusbar.window.StatusBarWindowView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/status_bar_launch_animation_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/system_bar_background" />
</com.android.systemui.statusbar.window.StatusBarWindowView>
2.4.@+id/status_bar_container
- 之后那个id为status_bar_container的布局会被替换成一个Fragment,
- 具体逻辑在上节5.1的下边这行代码
## CentralSurfacesImpl.java
// Set up CollapsedStatusBarFragment and PhoneStatusBarView
StatusBarInitializer initializer = mCentralSurfacesComponent.getStatusBarInitializer();
//...见补充1
initializer.initializeStatusBar(mCentralSurfacesComponent);
>1.StatusBarInitializer
StatusBarInitializer.kt通过replace方法,把布局里的容器换成了Fragment
fun initializeStatusBar(
centralSurfacesComponent: CentralSurfacesComponent
) {
windowController.fragmentHostManager.addTagListener(
CollapsedStatusBarFragment.TAG,
object : FragmentHostManager.FragmentListener {
override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
.statusBarFragmentComponent ?: throw IllegalStateException()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
statusBarFragmentComponent.phoneStatusBarView,//注解获取的
statusBarFragmentComponent.phoneStatusBarViewController,
statusBarFragmentComponent.phoneStatusBarTransitions
)
creationListeners.forEach { listener ->
listener.onStatusBarViewInitialized(statusBarFragmentComponent)
}
}
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
// nop
}
}).fragmentManager
.beginTransaction()
.replace(R.id.status_bar_container,//这里
centralSurfacesComponent.createCollapsedStatusBarFragment(),//替换的fragment
CollapsedStatusBarFragment.TAG)
.commit()
}
- centralSurfacesComponent.createCollapsedStatusBarFragment() 这个注解生成的fragment就是下边要讲的
3.CollapsedStatusBarFragment
3.1.onCreateView
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
>1.status_bar.xml
## 帧布局
<com.android.systemui.statusbar.phone.PhoneStatusBarView
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:id="@+id/status_bar"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
android:accessibilityPaneTitle="@string/status_bar"
>
<ImageView
android:id="@+id/notification_lights_out"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingBottom="2dip"
android:src="@drawable/ic_sysbar_lights_out_dot_small"
android:scaleType="center"
android:visibility="gone"
/>
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
android:orientation="horizontal">
<!-- Container for the entire start half of the status bar. It will always use the same
width, independent of the number of visible children and sub-children.
child分析见补充4-->
<FrameLayout
android:id="@+id/status_bar_start_side_container"
android:layout_height="match_parent"
android:layout_width="0dp"
android:clipChildren="false"
android:layout_weight="1">
<!-- Container that is wrapped around the views on the start half of the status bar.
Its width will change with the number of visible children and sub-children.
It is useful when we want to know the visible bounds of the content. -->
<FrameLayout
android:id="@+id/status_bar_start_side_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false">
<include layout="@layout/heads_up_status_bar_layout" />
<!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
and DISABLE_NOTIFICATION_ICONS, respectively -->
<LinearLayout
android:id="@+id/status_bar_start_side_except_heads_up"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:clipChildren="false">
<!--补充7-->
<ViewStub
android:id="@+id/operator_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/operator_name" />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:gravity="center_vertical|start"
/>
<include layout="@layout/ongoing_call_chip" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:clipChildren="false"/>
</LinearLayout>
</FrameLayout>
</FrameLayout>
<!-- Space should cover the notch (if it exists) and let other views lay out around it -->
<android.widget.Space
android:id="@+id/cutout_space_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
/>
<!-- Container for the entire end half of the status bar. It will always use the same
width, independent of the number of visible children and sub-children.
child分析见补充5-->
<FrameLayout
android:id="@+id/status_bar_end_side_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clipChildren="false">
<!-- Container that is wrapped around the views on the end half of the
status bar. Its width will change with the number of visible children and
sub-children.
It is useful when we want know the visible bounds of the content.-->
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/status_bar_end_side_content"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:orientation="horizontal"
android:gravity="center_vertical|end"
android:clipChildren="false">
<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" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</FrameLayout>
</LinearLayout>
<ViewStub
android:id="@+id/emergency_cryptkeeper_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/emergency_cryptkeeper_text"
/>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
>2.ongoing_call_chip.xml
<FrameLayout
android:id="@+id/ongoing_call_chip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
<LinearLayout
android:id="@+id/ongoing_call_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_gravity="center_vertical"
android:gravity="center"
android:background="@drawable/ongoing_call_chip_bg"
android:paddingStart="@dimen/ongoing_call_chip_side_padding"
android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
android:contentDescription="@string/ongoing_phone_call_content_description"
android:minWidth="@dimen/min_clickable_item_size"
>
<ImageView
android:src="@*android:drawable/ic_phone"
android:layout_width="@dimen/ongoing_call_chip_icon_size"
android:layout_height="@dimen/ongoing_call_chip_icon_size"
android:tint="?android:attr/colorPrimary"
/>
<com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
android:id="@+id/ongoing_call_chip_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center|start"
android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textColor="?android:attr/colorPrimary"
/>
</LinearLayout>
</FrameLayout>
>3.简化status_bar.xml
- 主要看下核心的第二层,里边有2个帧布局,比重是都是1
<com.android.systemui.statusbar.phone.PhoneStatusBarView
>
## 第一层
<ImageView
android:id="@+id/notification_lights_out"
/>
## 第二层
<LinearLayout android:id="@+id/status_bar_contents"
<!--内容分析见补充4-->
<FrameLayout
android:id="@+id/status_bar_start_side_container"
<android.widget.Space
android:id="@+id/cutout_space_view"
/>
<!--内容分析见补充5-->
<FrameLayout
android:id="@+id/status_bar_end_side_container"
</FrameLayout>
</LinearLayout>
## 第三层
<ViewStub
android:layout="@layout/emergency_cryptkeeper_text"
/>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
下边就具体来看下第二层布局里,左右两部分ui都有啥
>4.status_bar_start_side_container
状态栏左侧部分
<LinearLayout android:id="@+id/status_bar_start_side_except_heads_up" >
# sim卡运营商的名字,调用的getCarrierName方法,fragment里初始化的
<ViewStub android:id="@+id/operator_name"
android:layout="@layout/operator_name" />
# 当前时间
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock" />
# 通话时长
<include layout="@layout/ongoing_call_chip" />
# 其他通知icon
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"/>
</LinearLayout>
>5.status_bar_end_side_container
状态栏右侧部分
<FrameLayout
android:id="@+id/status_bar_end_side_container">
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/status_bar_end_side_content">
# 当前用户的信息,头像和名字
<include
android:id="@+id/user_switcher_container"
layout="@layout/status_bar_user_chip_container" />
#系统icons,见补充6
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</FrameLayout>
>6.system_icons.xml
可以看到,右侧固定有个电池图标
<LinearLayout
android:layout_gravity="center_vertical|end"
android:gravity="center_vertical">
## 这里是动态添加的icon
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:gravity="center_vertical"
android:orientation="horizontal"/>
## 固定有个电池控件
<com.android.systemui.battery.BatteryMeterView android:id="@+id/battery" />
</LinearLayout>
>7.operator_name.xml
服务商的名字或者通讯公司的名字?
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/operator_name_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
>
<!---就是个textview-->
<com.android.systemui.statusbar.OperatorNameView
android:id="@+id/operator_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:maxLength="20"
android:gravity="center_vertical|start"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:paddingEnd="5dp" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
3.2.onViewCreated
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//...
mStatusBarFragmentComponent.init();
//...
mStatusBar = (PhoneStatusBarView) view;
//...
//statusIcons就是右侧的icon容器,电量图标左侧的部分,见5.2
mDarkIconManager = mDarkIconManagerFactory.create(
view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
//...
//就是默认不显示的icon,从配置里读取,然后赋值给IconManager,显示的时候会判断过滤掉
updateBlockedIcons();//补充1
mStatusBarIconController.addIconGroup(mDarkIconManager);//具体逻辑见下边详情
//...
initNotificationIconArea();//状态栏左侧时间控件右边的通知图标容器
//...
}
>1.updateBlockedIcons
更新不显示的图标集合
void updateBlockedIcons() {
mBlockedIcons.clear();
//数据见补充2
List<String> blockList = Arrays.asList(getResources().getStringArray(
R.array.config_collapsed_statusbar_icon_blocklist));
//震动图标名字
String vibrateIconSlot = getString(com.android.internal.R.string.status_bar_volume);
//状态栏是否显示震动图标,
boolean showVibrateIcon =
mSecureSettings.getIntForUser(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
0,
UserHandle.USER_CURRENT) == 0;
// Filter out vibrate icon from the blocklist if the setting is on
for (int i = 0; i < blockList.size(); i++) {
if (blockList.get(i).equals(vibrateIconSlot)) {
if (showVibrateIcon) {
mBlockedIcons.add(blockList.get(i));
}
} else {
mBlockedIcons.add(blockList.get(i));
}
}
//见5.3.1把数据给IconManager
mMainExecutor.execute(() -> mDarkIconManager.setBlockList(mBlockedIcons));
}
>2.config_collapsed_statusbar_icon_blocklist
哪些图标不需要显示在收缩状态栏的,可以在这里配置
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
<item>@*android:string/status_bar_volume</item>
<item>@*android:string/status_bar_alarm_clock</item>
<item>@*android:string/status_bar_call_strength</item>
</string-array>
>3.initNotificationIconArea
public void initNotificationIconArea() {
//通知图标容器
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
//通知图标是这个controller里处理的
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView();
//移除旧的
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
//加入容器
notificationIconArea.addView(mNotificationIconAreaInner);
//根据disable状态更新通知图标的显示与否,以及来电通知图标。
updateNotificationIconAreaAndCallChip(mDisabled1, false);
}
3.3.StatusBarIconControllerImpl.java
>1.addIconGroup方法
public void addIconGroup(IconManager group) {
//...
group.setController(this);
mIconGroups.add(group);
List<Slot> allSlots = mStatusBarIconList.getSlots();
for (int i = 0; i < allSlots.size(); i++) {
Slot slot = allSlots.get(i);
List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder();
boolean hidden = mIconHideList.contains(slot.getName());
//这里刚开始holders集合是空的,所以啥也没干
for (StatusBarIconHolder holder : holders) {
int viewIndex = mStatusBarIconList.getViewIndex(slot.getName(), holder.getTag());
group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
}
}
}
3.4..StatusBarIconList
这个对象是注解生成的,数据是配置里读取的,相关的数组在frameworks/base/core/res/res/values/config.xml 数组长度有30多个,就不贴了。
>1.注解生成对象
@Provides
@SysUISingleton
static StatusBarIconList provideStatusBarIconList(Context context) {
return new StatusBarIconList(
context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
}
>2.构造方法
public class StatusBarIconList {
private final ArrayList<Slot> mSlots = new ArrayList<>();
private final List<Slot> mViewOnlySlots = Collections.unmodifiableList(mSlots);
public StatusBarIconList(String[] slots) {
final int N = slots.length;
for (int i = 0; i < N; i++) {
mSlots.add(new Slot(slots[i], null));//就个名字
}
}
/** Returns the list of current slots. */
public List<Slot> getSlots() {
return mViewOnlySlots;
}
>3.getHolderListInViewOrder
可以看到,默认的Slot就名字不是空的,其他都是空的,所以下边if条件都不满足。
public List<StatusBarIconHolder> getHolderListInViewOrder() {
ArrayList<StatusBarIconHolder> holders = new ArrayList<>();
if (mSubSlots != null) {
for (int i = mSubSlots.size() - 1; i >= 0; i--) {
holders.add(mSubSlots.get(i));
}
}
if (mHolder != null) {
holders.add(mHolder);
}
return holders;
}
3.5.PhoneStatusBarView
这个类主要计算下status bar的高度,监听下clock,battery图标的黑暗颜色变化,还有cutout_space_view的大小动态设置
public class PhoneStatusBarView extends FrameLayout {
4.NotificationIconAreaController.java
4.1.initializeNotificationAreaViews
构造方法里调用
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
}
>1.inflateIconArea
protected View inflateIconArea(LayoutInflater inflater) {
return inflater.inflate(R.layout.notification_icon_area, null);
}
>2.notification_icon_area.xml
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/notification_icon_area_inner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<!--自定义的ViewGroup-->
<com.android.systemui.statusbar.phone.NotificationIconContainer
android:id="@+id/notificationIcons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:clipChildren="false"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
4.2.updateNotificationIcons
- 外部调用,有新的通知的时候,测试了一下,新加一条通知,这个方法可能调用好几次,集合数据个数也可能变化好几次。
- 这就牵扯到数据来源了,它和下拉看到的通知栏效果相关的,比如新加一条通知,会显示在下拉通知栏,完事可能会合并成一个,这时候通知数据也变化了。
public void updateNotificationIcons(List<ListEntry> entries) {
mNotificationEntries = entries;
updateNotificationIcons();
}
//更新不同的通知,容器不一样,走的都是4.3的方法
private void updateNotificationIcons() {
updateStatusBarIcons();//补充1
updateShelfIcons();//就是下拉通知栏,通知太多显示不下,底部会显示看不到的通知图标。
updateAodNotificationIcons();//这个是锁屏界面时钟下边的,不一定会显示,容器可能是gone
applyNotificationIconsTint();//给icon染色,
}
>1.updateStatusBarIcons
public void updateStatusBarIcons() {
updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
false /* showAmbient */,
mShowLowPriority,
true /* hideDismissed */,
true /* hideRepliedMessages */,
false /* hideCurrentMedia */,
false /* hidePulsing */);
}
4.3.updateIconsForLayout
private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
boolean hidePulsing) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
// 过滤掉媒体通知以及子通知
for (int i = 0; i < mNotificationEntries.size(); i++) {
NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
if (entry != null && entry.getRow() != null) {
//这里判断过滤掉不需要显示的通知
if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages, hideCurrentMedia, hidePulsing)) {
//加入集合
StatusBarIconView iconView = function.apply(entry);
if (iconView != null) {
toShow.add(iconView);
}
}
}
}
ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
ArrayList<View> toRemove = new ArrayList<>();
//循环容器里已有的通知图标
for (int i = 0; i < hostLayout.getChildCount(); i++) {
View child = hostLayout.getChildAt(i);
if (!(child instanceof StatusBarIconView)) {
continue;
}
//child不在要显示的通知集合里,也就是需要移除的
if (!toShow.contains(child)) {
//默认false
boolean iconWasReplaced = false;
StatusBarIconView removedIcon = (StatusBarIconView) child;
//要移除的图标groupKey
String removedGroupKey = removedIcon.getNotification().getGroupKey();
for (int j = 0; j < toShow.size(); j++) {
StatusBarIconView candidate = toShow.get(j);
//要移除的child和要显示的比较:icon一样,groupkey一样
if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
&& candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
//修改标志,取反。
//有单数个一样的,replace为true,有偶数个一样的,replace还是false
if (!iconWasReplaced) {
iconWasReplaced = true;
} else {
iconWasReplaced = false;
break;
}
}
}
//最终如果需要替换的话
if (iconWasReplaced) {
//放入要替换的map里,key是groupKey
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
if (statusBarIcons == null) {
statusBarIcons = new ArrayList<>();
replacingIcons.put(removedGroupKey, statusBarIcons);
}
statusBarIcons.add(removedIcon.getStatusBarIcon());
}
//加入集合
toRemove.add(removedIcon);
}
}
//移除所有重复的
ArrayList<String> duplicates = new ArrayList<>();
for (String key : replacingIcons.keySet()) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
//不为1,说明同一个groupKey下有多个通知
if (statusBarIcons.size() != 1) {
duplicates.add(key);
}
}
replacingIcons.removeAll(duplicates);//把重复的从map里删除
hostLayout.setReplacingIcons(replacingIcons);//给容器设置新的数据
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
hostLayout.removeView(toRemove.get(i));//容器移除需要删除的view
}
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
StatusBarIconView v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
if (hideDismissed) {
v.setOnDismissListener(mUpdateStatusBarIcons);//监听回调就是4.2.1
}
hostLayout.addView(v, i, params);//添加新的
}
}
hostLayout.setChangingViewPositions(true);
//按照通知的顺序重新排序child
final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = hostLayout.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
hostLayout.removeView(expected);
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
hostLayout.setReplacingIcons(null);//置空数据
}
5.DarkIconManager
继承IconManager,主要处理icon的动态添加逻辑
5.1.构造方法
public DarkIconManager(
LinearLayout linearLayout,
StatusBarLocation location,
//...
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
5.2.工厂模式Factory.create
create方法第一个参数,就是icon要添加到的viewGroup
public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
return new DarkIconManager(
group,
location,
5.3.IconManager
public IconManager(
ViewGroup group,
StatusBarLocation location,
//...
) {
mGroup = group;
mStatusBarPipelineFlags = statusBarPipelineFlags;
>1. setBlockList
设置需要隐藏的图标集合
public void setBlockList(@Nullable List<String> blockList) {
mBlockList.clear();
mBlockList.addAll(blockList);
if (mController != null) {
mController.refreshIconGroup(this);
}
}
>2.onIconAdded
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
addHolder(index, slot, blocked, holder);
}
>3.addHolder
- 可以看到blocked字段对wifi和mobile图标是不生效的。
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
// This is a little hacky, and probably regrettable, but just set `blocked` on any icon
// that is in our blocked list, then we'll never see it
if (mBlockList.contains(slot)) {
//需要隐藏
blocked = true;
}
//下边add方法就是把view添加到mGroup里
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI:
return addWifiIcon(index, slot, holder.getWifiState());
case TYPE_WIFI_NEW:
return addNewWifiIcon(index, slot);
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
case TYPE_MOBILE_NEW:
return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
}
找不到icon哪里添加的,那从最基本的来找,就是前边的贴的IconManager里有addView的方法,所以查看哪里调用即可。我们Fragment里用到的是DarkIconManager,查下里边的onIconAdded方法都哪里用到,很容易找到StatusBarIconControllerImpl.java里有用到,这个类里有很多setIcon的方法,找下哪里用的, 最后发现icon都在下边2个XXXPolicy类里添加的。
6.PhoneStatusBarPolicy(普通图标)
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
6.1.初始化
这个类是CentralSurfacesImpl的构造方法里生成的,然后在其start方法里init的,如下
//上一篇说过的,这个start会自动执行的
public void start() {
//...
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
CentralSurfacesImpl是实现接口CoreStartable的,会自动执行start方法
public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
>1.init方法
- 可以看到里边添加的icon都默认不可见的,这里应该就是初始化下icon数据,
- 方法最后是一些监听回调,会动态修改图标的可见性。
// TTY status: teletypewriter,电传打字机的缩写,指的是一种用于文本通信的设备
updateTTY();
// bluetooth status ,看了下逻辑,只有在蓝牙连接并且audio正常的情况下才有这个图标
updateBluetooth();
// Alarm clock
mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
mIconController.setIconVisibility(mSlotAlarmClock, false);
// zen
mIconController.setIcon(mSlotZen, R.drawable.stat_sys_dnd, null);
mIconController.setIconVisibility(mSlotZen, false);
// vibrate
mIconController.setIcon(mSlotVibrate, R.drawable.stat_sys_ringer_vibrate,
mResources.getString(R.string.accessibility_ringer_vibrate));
mIconController.setIconVisibility(mSlotVibrate, false);
// mute
mIconController.setIcon(mSlotMute, R.drawable.stat_sys_ringer_silent,
mResources.getString(R.string.accessibility_ringer_silent));
mIconController.setIconVisibility(mSlotMute, false);
updateVolumeZen();
// cast
mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
mIconController.setIconVisibility(mSlotCast, false);
// hotspot
mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
mResources.getString(R.string.accessibility_status_bar_hotspot));
mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
// managed profile
updateManagedProfile();
// data saver
mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
mResources.getString(R.string.accessibility_data_saver_on));
mIconController.setIconVisibility(mSlotDataSaver, false);
// privacy items
String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
String microphoneDesc = mResources.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
microphoneDesc);
mIconController.setIconVisibility(mSlotMicrophone, false);
String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
String cameraDesc = mResources.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
cameraDesc);
mIconController.setIconVisibility(mSlotCamera, false);
mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
mResources.getString(R.string.accessibility_location_active));
mIconController.setIconVisibility(mSlotLocation, false);
// sensors off
mIconController.setIcon(mSlotSensorsOff, R.drawable.stat_sys_sensors_off,
mResources.getString(R.string.accessibility_sensors_off_active));
mIconController.setIconVisibility(mSlotSensorsOff,
mSensorPrivacyController.isSensorPrivacyEnabled());
// screen record
mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
mIconController.setIconVisibility(mSlotScreenRecord, false);
//下边这些回调,会动态修改图标的可见性
mRotationLockController.addCallback(this);
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
mZenController.addCallback(this);
mCast.addCallback(mCastCallback);
mHotspot.addCallback(mHotspotCallback);
mNextAlarmController.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
mKeyguardStateController.addCallback(this);
mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
mRecordingController.addCallback(this);
mCommandQueue.addCallback(this);
}
7.StatusBarSignalPolicy(信号图标)
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
7.1.初始化
同上,也是CentralSurfacesImpl.java构造方法里实例化对象,start方法里执行init方法
public void start() {
//...
mStatusBarSignalPolicy.init();
>1.init方法
在监听里处理icon的
public void init() {
if (mInitialized) {
return;
}
mInitialized = true;
mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
mNetworkController.addCallback(this);
mSecurityController.addCallback(this);
}
看下这个类用到的字符串,就知道他都处理哪些图标了
mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile);
mSlotWifi = mContext.getString(com.android.internal.R.string.status_bar_wifi);
mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
mSlotVpn = mContext.getString(com.android.internal.R.string.status_bar_vpn);
mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
mSlotCallStrength =
mContext.getString(com.android.internal.R.string.status_bar_call_strength);
mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
8.StatusBarIconControllerImpl
public class StatusBarIconControllerImpl implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
8.1.setIcon
很多setIcon的方法,如下图
这里就拿其中一种来分析下流程,默认的holder是null的,所以下边的代码会进入if,创建新的holder
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
if (holder == null) {
//实例化一个icon对象
StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(
mContext, resourceId), 0, 0, contentDescription);
//创建holder对象,并把icon赋值给holder对象
holder = StatusBarIconHolder.fromIcon(icon);
setIcon(slot, holder);
} else {
//更新下icon信息即可
holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
holder.getIcon().contentDescription = contentDescription;
handleSet(slot, holder);
}
}
>1.StatusBarIconHolder.fromIcon(icon)
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
StatusBarIconHolder wrapper = new StatusBarIconHolder();
wrapper.mIcon = icon;
return wrapper;
}
>2.setIcon
前边分析过mStatusBarIconList时注解生成的,默认里边的holder是null的,所以这里判断下,为null就addIcon,不为null就做更新操作
private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
boolean isNew = mStatusBarIconList.getIconHolder(slot, holder.getTag()) == null;
mStatusBarIconList.setIcon(slot, holder);//给对应的icon设置holder
if (isNew) {
addSystemIcon(slot, holder);
} else {
handleSet(slot, holder);
}
}
继续
private void addSystemIcon(String slot, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
boolean hidden = mIconHideList.contains(slot);
//这个是添加新的icon view,mIconGroups里目前已知的是那个 mDarkIconManager
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
>3.handleSet
private void handleSet(String slotName, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
//这里是找到就的icon view,更新其显示的图标
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
8.2.Icon的种类
主要就普通的icon和特殊的icon(wifi,mobile)
>1.普通的icon
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
view.set(icon);
mGroup.addView(view, index, onCreateLayoutParams());
return view;
}
private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
return new StatusBarIconView(mContext, slot, null, blocked);
}
StatusBarIconView
public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {
view.set(icon)的方法,看下可见性的处理。可以看到,需要是icon本身可见并且block为false。
if (!visibilityEquals) {
setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
return true;
}
>2.特殊的icon(wifi,mobile)
wifi ,new wifi ,mobile ,new mobile 是4种不同的view
final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
>>新旧icon显示的判断
那么到底显示新的还是旧的,咋判断的?如下,可以看到是statusBarPipelineFlags这个类决定的
public IconManager(
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mGroup = group;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
mLocation = location;
//是否是新的mobile icon
if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
}
//是否是新的wifi icon
if (statusBarPipelineFlags.runNewWifiIconBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
} else {
mWifiViewModel = null;
}
}
继续,可以看到,又是由FeatureFlags决定,这个东西是个接口,有两种实现
class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
fun runNewMobileIconsBackend(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
fun runNewWifiIconBackend(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
如下,都是false
public class FeatureFlagsRelease implements FeatureFlags {
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
return false;
}
debug模式下,可以看到直接用的就是参数flag的默认的值,具体的可以点进去用到的flag查看,默认都是false的
public class FeatureFlagsDebug implements FeatureFlags {
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
return isEnabledInternal(flag);
}
private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
mBooleanFlagCache.put(id,
readBooleanFlagInternal(flag, flag.getDefault()));
}
return mBooleanFlagCache.get(id);
}
>>addMobileIcon
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
StatusBarMobileView view = StatusBarMobileView
.fromContext(mobileContext, slot);
return view;
public static StatusBarMobileView fromContext(
Context context,
String slot
) {
LayoutInflater inflater = LayoutInflater.from(context);
StatusBarMobileView v = (StatusBarMobileView)
inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
v.setSlot(slot);
v.init();
v.setVisibleState(STATE_ICON);
return v;
}
布局看下
<com.android.systemui.statusbar.StatusBarMobileView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mobile_combo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" >
<include layout="@layout/status_bar_mobile_signal_group_inner" />
</com.android.systemui.statusbar.StatusBarMobileView>
status_bar_mobile_signal_group_inner.xml
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto" >
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/mobile_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal" >
//移动数据的接收发送,图片是向上向下的箭头
<FrameLayout
android:id="@+id/inout_container"
android:layout_height="17dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/mobile_in"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_down"
android:visibility="gone"
android:paddingEnd="2dp"
/>
<ImageView
android:id="@+id/mobile_out"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_up"
android:paddingEnd="2dp"
android:visibility="gone"
/>
</FrameLayout>
//移动数据的类型
<ImageView
android:id="@+id/mobile_type"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="2.5dp"
android:paddingEnd="1dp"
android:visibility="gone" />
<Space
android:id="@+id/mobile_roaming_space"
android:layout_height="match_parent"
android:layout_width="@dimen/roaming_icon_start_padding"
android:visibility="gone"
/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<com.android.systemui.statusbar.AnimatedImageView
android:id="@+id/mobile_signal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
systemui:hasOverlappingRendering="false"
/>
//小的漫游图标,左上角一个R
<ImageView
android:id="@+id/mobile_roaming"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stat_sys_roaming"
android:contentDescription="@string/data_connection_roaming"
android:visibility="gone" />
</FrameLayout>
//大的漫游图标,居中的R
<ImageView
android:id="@+id/mobile_roaming_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stat_sys_roaming_large"
android:contentDescription="@string/data_connection_roaming"
android:visibility="gone" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</merge>
addWifiIcon
private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
return view;
}
public static StatusBarWifiView fromContext(Context context, String slot) {
LayoutInflater inflater = LayoutInflater.from(context);
StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
v.setSlot(slot);
v.init();
v.setVisibleState(STATE_ICON);
return v;
}
8.3.onTuningChanged
public void onTuningChanged(String key, String newValue) {
if (!ICON_HIDE_LIST.equals(key)) {
return;
}
mIconHideList.clear();
//数据见补充1
mIconHideList.addAll(StatusBarIconController.getIconHideList(mContext, newValue));
>1.getIconHideList
static ArraySet<String> getIconHideList(Context context, String hideListStr) {
ArraySet<String> ret = new ArraySet<>();
//没有提供hide数据,就用默认配置的,有的话就解析
String[] hideList = hideListStr == null
? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)//见3.2.1
: hideListStr.split(",");
for (String slot : hideList) {
if (!TextUtils.isEmpty(slot)) {
ret.add(slot);
}
}
return ret;
}
8.4.addSystemIcon
private void addSystemIcon(String slot, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
//看slot是否在要隐藏的集合里
boolean hidden = mIconHideList.contains(slot);
//见5.3.2,添加图标到容器
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
9.BatteryMeterView
状态栏最右侧的电池图标
public class BatteryMeterView extends LinearLayout implements DarkReceiver {
9.1.构造方法
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//水平方向
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
defStyle, 0);
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
//显示百分比是否可用,配置里的,默认是true
mShowPercentAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
setupLayoutTransition();
//电池图标
mBatteryIconView = new ImageView(context);
mBatteryIconView.setImageDrawable(mDrawable);
final MarginLayoutParams mlp = new MarginLayoutParams(
getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
//右侧有个margin
mlp.setMargins(0, 0, 0,
getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
addView(mBatteryIconView, mlp);
//见9.2
updateShowPercent();
mDualToneHandler = new DualToneHandler(context);
// Init to not dark at all.
onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
setClipChildren(false);
setClipToPadding(false);
}
>1.mShowPercentMode
显示百分比的模式有4种
private int mShowPercentMode = MODE_DEFAULT;
public static final int MODE_DEFAULT = 0;
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
9.2.updateShowPercent
更新是否显示电池百分比
void updateShowPercent() {
//百分比控件不为空,说明正在显示,否则没有显示
final boolean showing = mBatteryPercentView != null;
//先读取系统设置,是否允许显示
final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT));
boolean shouldShow =
//状态栏的图标正常应该是这行条件
(mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
//后边两种mode的改变是下拉状态栏的图标
|| mShowPercentMode == MODE_ON
|| mShowPercentMode == MODE_ESTIMATE;
//电池状态不是未知的
shouldShow = shouldShow && !mBatteryStateUnknown;
if (shouldShow) {
if (!showing) {
//见补充1,加载百分比控件
mBatteryPercentView = loadPercentView();
if (mPercentageStyleId != 0) { // Only set if specified as attribute
mBatteryPercentView.setTextAppearance(mPercentageStyleId);
}
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
//见补充2
updatePercentText();
//添加文本到容器
addView(mBatteryPercentView,
new ViewGroup.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
}
} else {
//不应该显示,并且正在显示,移除
if (showing) {
removeView(mBatteryPercentView);
mBatteryPercentView = null;
}
}
}
>1.loadPercentView
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
}
>2.updatePercentText
更新百分比文本内容
void updatePercentText() {
if (mBatteryStateUnknown) {
//电池状态未知
return;
}
//电池估算获取器为空
if (mBatteryEstimateFetcher == null) {
//见补充3
setPercentTextAtCurrentLevel();
return;
}
//百分比控件不为空(说明正在显示中)
if (mBatteryPercentView != null) {
//模式是 estimate并且非充电中
if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
//获取估算的电池信息
mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
(String estimate) -> {
if (mBatteryPercentView == null) {
return;
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
//估算模式,有值返回,直接显示
mEstimateText = estimate;
mBatteryPercentView.setText(estimate);
updateContentDescription();
} else {
//补充3
setPercentTextAtCurrentLevel();
}
});
} else {
setPercentTextAtCurrentLevel();
}
} else {
updateContentDescription();
}
}
>3.setPercentTextAtCurrentLevel
根据当前的level,设置百分比
private void setPercentTextAtCurrentLevel() {
if (mBatteryPercentView != null) {
mEstimateText = null;
//格式化为 x%
String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
mBatteryPercentView.setText(percentText);
}
}
updateContentDescription();
}
9.3.setPercentShowMode
此方法调用见9.4
public void setPercentShowMode(@BatteryPercentMode int mode) {
if (mode == mShowPercentMode) return;
mShowPercentMode = mode;
updateShowPercent();//见9.2
updatePercentText();//见9.2.2
}
9.4.QuickStatusBarHeader.java
>1.onFinishInflate
protected void onFinishInflate() {
//..
// QS will always show the estimate, and BatteryMeterView handles the case where
// it's unavailable or charging
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
>2.updateBatteryMode
更新batteryMode,有两处调用:
- onApplyWindowInsets方法
- updateResources方法 参数说明:
- mConfigShowBatteryEstimate 配置里,默认是false,390dp以上是true
- mHasCenterCutout 就是圆角有没有裁剪过
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
} else {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON);
}
}
9.5.onBatteryLevelChanged
电量变化回调,是在controller里调用的
void onBatteryLevelChanged(int level, boolean pluggedIn) {
mDrawable.setCharging(pluggedIn);
mDrawable.setBatteryLevel(level);
mCharging = pluggedIn;
mLevel = level;
updatePercentText();
}
9.6.onIsOverheatedChanged
过热状态
void onIsOverheatedChanged(boolean isOverheated) {
boolean valueChanged = mIsOverheated != isOverheated;
mIsOverheated = isOverheated;
if (valueChanged) {
updateContentDescription();
// 过热状态的图标和正常的不一样,更新
scaleBatteryMeterViews();
}
}
10.BatteryMeterViewController.java
10.1.构造方法
//设置电池评估获取器
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
//是否显示电池保护图标,默认配置是false
mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
mSettingObserver = new SettingObserver(mMainHandler);
}
10.2.onViewAttached
protected void onViewAttached() {
mConfigurationController.addCallback(mConfigurationListener);
subscribeForTunerUpdates();
//添加电池状态变化图标,见10.3
mBatteryController.addCallback(mBatteryStateChangeCallback);
//监听settings改变
registerShowBatteryPercentObserver(mUserTracker.getUserId());
registerGlobalBatteryUpdateObserver();
mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
mView.updateShowPercent();
}
>1.监听settings改变
observer见补充2
private void registerShowBatteryPercentObserver(int user) {
mContentResolver.registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT),
false,
mSettingObserver,
user);
}
private void registerGlobalBatteryUpdateObserver() {
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
false,
mSettingObserver);
}
>2.SettingObserver
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
//更新是否显示百分比
mView.updateShowPercent();
if (TextUtils.equals(uri.getLastPathSegment(),
Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
//更新百分比文本内容
mView.updatePercentText();
}
}
10.3.mBatteryStateChangeCallback
private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
new BatteryController.BatteryStateChangeCallback() {
@Override// 电量变化
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mView.onBatteryLevelChanged(level, pluggedIn);
}
@Override//省电模式变化
public void onPowerSaveChanged(boolean isPowerSave) {
mView.onPowerSaveChanged(isPowerSave);
}
@Override//电池状态是否未知
public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(isUnknown);
}
@Override//是否过热
public void onIsOverheatedChanged(boolean isOverheated) {
mView.onIsOverheatedChanged(isOverheated);
}
};
11.总结
这篇主要介绍了状态栏(非展开状态)的图标显示逻辑。
- StatusBarView的添加
- 通过CollapsedStatusBarFragment管理icon的显示
- icon的管理类有2个,PhoneStatusBarPollicy以及StatusBarSignalPolicy
- 可以在string里配置哪些icon不用显示