Android SystemUI——CarSystemBar视图解析(十一)
前面文章我们已经把 CarSystemBar 从启动到构建视图,再到将视图添加到 Window 的流程分析完毕,我们知道默认情况下在车载系统中只显示顶部栏和底部栏视图的。这里我们在前面文章的基础上以顶部栏为例具体解析其视图的结构。
一、顶部栏解析
通过《CarSystemBar车载状态栏》这篇文章我们知道车载系统对应的顶部状态栏为 mTopSystemBarView,其布局文件为 car_top_system_bar。
1、car_top_system_bar.xml
源码位置:/packages/apps/Car/SystemUI/res/layout/car_top_system_bar.xml
<com.android.systemui.car.systembar.CarSystemBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/car_top_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/system_bar_background"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layoutDirection="ltr">
<FrameLayout
android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/car_padding_2"
android:layout_marginStart="@dimen/car_padding_2"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:gravity="center_vertical"
>
<com.android.systemui.car.systembar.CarSystemBarButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/system_bar_background_pill"
android:layout_weight="1"
android:gravity="center_vertical"
systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end">
<include
layout="@layout/system_icons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/car_padding_2"
android:layout_marginEnd="@dimen/car_padding_2"
android:gravity="center_vertical"
/>
</com.android.systemui.car.systembar.CarSystemBarButton>
</FrameLayout>
<com.android.systemui.car.systembar.CarSystemBarButton
android:id="@+id/clock_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/system_bar_pill_rotary_background"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
android:layout_centerInParent="true"
systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end">
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="5dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SystemBar.Clock"
systemui:amPmStyle="normal"
/>
</com.android.systemui.car.systembar.CarSystemBarButton>
<include layout="@layout/mic_privacy_chip"
android:layout_width="@dimen/privacy_chip_width"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/clock_container"
android:layout_toLeftOf="@id/user_name_container" />
<FrameLayout
android:id="@+id/user_name_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginTop="@dimen/car_padding_2"
>
<com.android.systemui.car.systembar.CarSystemBarButton
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/car_padding_2"
android:background="@drawable/system_bar_background_pill"
android:gravity="center_vertical"
systemui:intent="intent:#Intent;component=com.android.car.settings/.profiles.ProfileSwitcherActivity;launchFlags=0x24000000;end"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_marginStart="@dimen/car_padding_2"
android:layout_marginEnd="@dimen/car_padding_2"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/user_avatar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/car_ic_user_icon"
android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
/>
<TextView
android:id="@+id/user_name_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.SystemBar.Username"
android:singleLine="true"
android:maxWidth="@dimen/car_system_bar_user_name_max_width"
android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
/>
</LinearLayout>
</com.android.systemui.car.systembar.CarSystemBarButton>
</FrameLayout>
</RelativeLayout>
</com.android.systemui.car.systembar.CarSystemBarView>
其实 car_top_system_bar 所构建是视图其实就是车载系统最终显示在桌面上的状态栏视图。
2、CarSystemBarViewFactory
通过前面状态栏视图的构建构成我们知道,最终调用了 CarSystemBarViewFactory 的 getBarCached() 方法。
源码位置:/packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarViewFactory.java
getBarCached
private CarSystemBarView getBarCached(Type type, @LayoutRes int barLayout) {
……
CarSystemBarView view = (CarSystemBarView) View.inflate(mContext, barLayout,
/* root= */ null);
// 设置图标控制器
view.setupIconController(mFeatureFlags, mIconController);
view.addView(new FocusParkingView(mContext), 0);
mCachedViewMap.put(type, view);
return mCachedViewMap.get(type);
}
这里调用了 setupIconController() 方法为存储快捷图标的容器控件设置控制器。
3、CarSystemBarView
源码位置:/packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarView.java
setupIconController
void setupIconController(FeatureFlags featureFlags, StatusBarIconController iconController) {
// 查找状态图标容器
View mStatusIcons = findViewById(R.id.statusIcons);
if (mStatusIcons != null) {
// 如果标准容器在视图中,则为状态图标(如wifi和蓝牙)附加控制器
// 创建 DarkIconManager,它负责管理状态图标的显示
StatusBarIconController.DarkIconManager mDarkIconManager =
new StatusBarIconController.DarkIconManager(
mStatusIcons.findViewById(R.id.statusIcons), featureFlags);
mDarkIconManager.setShouldLog(true);
iconController.addIconGroup(mDarkIconManager);
}
}
该方法用于配置和初始化状态栏图标控制器(StatusBarIconController),以便管理状态栏中的图标,例如 Wi-Fi、蓝牙等连接状态的图标。同时可以看到图标容器的 id 为 statusIcons,位于 car_top_system_bar.xml 中的引用布局 system_icons 内。
system_icons.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/system_icons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingEnd="4dp"
android:gravity="center_vertical"
android:orientation="horizontal"
/>
</LinearLayout>
可以看到图标容器是一个 StatusIconContainer 自定义的控件。下面来看一下上面的 addIconGroup() 方法,该方法在 StatusBarIconController 定义,真正实现是在 StatusBarIconControllerImpl 中。
4、StatusBarIconControllerImpl
源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
addIconGroup
@Override
public void addIconGroup(IconManager group) {
// 将新的 IconManager 添加到管理列表中
mIconGroups.add(group);
// 获取所有槽位列表
List<Slot> allSlots = 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());
for (StatusBarIconHolder holder : holders) {
// 获取图标的标识符
int tag = holder.getTag();
// 计算图标在视图中的索引位
int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
// 通知 IconManager 图标已被添加
group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
}
}
}
该方法有效地将新的 IconManager 集成到现有的图标管理系统中,并确保它可以立即开始管理工作。最终调用 onIconAdded 通知图标已经添加。
5、StatusBarIconController
源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
onIconAdded
class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
@Override
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
}
}
这里先调用了 addHolder() 方法来添加图标持有者,然后调用了 DarkIconDispatcher 的 addDarkReceiver() 方法,用于可以接收深色模式的变化通知。
addHolder
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
// 检查当前槽位是否存在于屏蔽列表中
if (mBlockList.contains(slot)) {
blocked = true;
}
// 根据图标类型调用相应的方法来添加图标
switch (holder.getType()) {
case TYPE_ICON: // 普通图标
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI: // Wi-Fi信号图标
return addSignalIcon(index, slot, holder.getWifiState());
case TYPE_MOBILE: // 移动网络图标
return addMobileIcon(index, slot, holder.getMobileState());
}
return null;
}
该方法负责处理状态栏中各种类型的图标添加逻辑。它不仅确保了图标能够根据其类型正确地显示,还提供了对特定图标进行阻止显示的能力。 可以看到这里的 switch 中包含三个图标控件的处理,这里我们以移动网络图标为例继续看一下。
@VisibleForTesting
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
// 创建移动网络图标视图
StatusBarMobileView view = onCreateStatusBarMobileView(slot);
// 应用移动网络状态到视图
view.applyMobileState(state);
// 将视图添加到视图组中
mGroup.addView(view, index, onCreateLayoutParams());
// 如果处于演示模式,则更新演示状态
if (mIsInDemoMode) {
mDemoStatusIcons.addMobileView(state);
}
return view;
}
通过 @VisibleForTesting 注释我们知道该方法是为了测试目的而设计的,实际开发中我们需要根据自己的需求去创建需要的对象。这里的 StatusBarMobileView 位于 /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ 下,这里我们就不在详细分析了。
6、DarkIconDispatcherImpl
再来看一下深色模式状态变化的通知流程,DarkIconDispatcher 中只定义了接口,真正实现该接口是在 DarkIconDispatcherImpl 中。
源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
addDarkReceiver
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
}
该方法调用 DarkReceiver 中的 onDarkChanged() 方法,真正的通知到控件深色模式状态变化。