一、状态栏组件的启动流程
1、SystemUIApplication启动SystemUI组件的关键代码
framework/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public class SystemUIApplication extends Application implements SysUiServiceProvider {
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
startServicesIfNeeded(names);
}
private void startServicesIfNeeded(String[] services) {
mServices = new SystemUI[services.length];
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];//具体系统组件类的完整路径
Class cls = Class.forName(clsName);
mServices[i] = (SystemUI) cls.newInstance();//通过反射创建实例对象
mServices[i].start(); //调用start()启动
}
}
}
frameworks/base/packages/SystemUI/res/values/config.xml
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.Dependency</item>
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
<item>com.android.systemui.keyguard.KeyguardViewMediator</item>
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.SystemBars</item>
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>
<item>com.android.systemui.media.RingtonePlayer</item>
<item>com.android.systemui.keyboard.KeyboardUI</item>
<item>com.android.systemui.pip.PipUI</item>
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
<item>@string/config_systemUIVendorServiceComponent</item>
<item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
<item>com.android.systemui.LatencyTester</item>
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
</string-array>
2、SystemBars启动StatusBar组件关键代码
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
public class SystemBars extends SystemUI {
...
@Override
public void start() {
createStatusBarFromConfig();//通过配置创建系统状态栏
}
private void createStatusBarFromConfig() {
final String clsName = mContext.getString(R.string.config_statusBarComponent);//获取系统状态栏对应类的全路径
Class<?> cls = mContext.getClassLoader().loadClass(clsName);//加载对应的类对象
mStatusBar = (SystemUI) cls.newInstance();//通过反射创建实例对象
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
mStatusBar.start();//调用系统状态栏的start方法
}
frameworks/base/packages/SystemUI/res/values/config.xml
<string name="config_statusBarComponent">com.android.systemui.statusbar.phone.StatusBar</string>
3、StatusBar加载布局文件super_status_bar.xml的关键代码
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
protected StatusBarWindowView mStatusBarWindow;//状态栏窗口
protected PhoneStatusBarView mStatusBarView;//手机状态栏视图
@Override
public void start() {
createAndAddWindows(); //创建整个SystemUI视图并添加到WindowManager中
}
public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView(); //创建整个SystemUI视图
}
//构建组件视图
protected void makeStatusBarView() {
final Context context = mContext;
//实例化整个SystemUI视图,包括状态栏,通知面版,锁屏
inflateStatusBarWindow(context);
...
//实例化状态栏视图
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {})
.getFragmentManager()
.beginTransaction()
//这里和id为status_bar_container的FrameLayout控件进行关联
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
.commit();
...
}
protected void inflateStatusBarWindow(Context context) {
//实例化整个SystemUI视图,包括状态栏,通知面版,锁屏
mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
}
}
frameworks/base/packages/SystemUI/res/layout/super_status_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
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.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
<!--原生状态栏就存放在这里-->
<FrameLayout
android:id="@+id/status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout="@layout/car_fullscreen_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!--下拉状态栏、锁屏等布局就存放在这里-->
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<include layout="@layout/brightness_mirror" />
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
以上我们简单分析了SystemUI模块状态栏组件从启动到加载状态栏布局的过程,接下来我们开始具体定制状态栏和导航栏。
二、定制状态栏
1、修改默认的状态栏类路径配置字段config_statusBarComponent
frameworks/base/packages/SystemUI/res/values/config.xml
<string name="config_statusBarComponent">com.leapmotor.systemui.LeapMotorCarStatusBar</string>
在修改这个字段以后,Android系统开机之后,便会启动LeapMotorCarStatusBar 这个类。
2、新建继承自StatusBar的状态栏类LeapMotorCarStatusBar
frameworks/base/packages/SystemUI/src/com/leapmotor/systemui/LeapMotorCarStatusBar .java
public class LeapMotorCarStatusBar extends StatusBar{
//重写StatusBar的inflateStatusBarWindow方法,创建自定义状态栏视图对象。
@Override
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
FrameLayout flLeapMotor = mStatusBarWindow.findViewById(R.id.fl_leapmotor_status_bar);
LeapMotorStatusBarViewHelper statusBarHelper = new LeapMotorStatusBarViewHelper(mStatusBarWindow.getContext());
flLeapMotor.addView(statusBarHelper.getRootView());
}
}
frameworks/base/packages/SystemUI/res/layout/super_status_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.phone.StatusBarWindowView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
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:layout_height="match_parent"
android:scaleType="centerCrop" />
<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.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:visibility="invisible"
sysui:ignoreRightInset="true" />
<FrameLayout
android:id="@+id/status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible" />
<ViewStub
android:id="@+id/fullscreen_user_switcher_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/car_fullscreen_user_switcher"
android:visibility="invisible" />
<include
layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<include layout="@layout/brightness_mirror" />
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:visibility="invisible"
sysui:ignoreRightInset="true" />
<!--自定义状态栏就存放在这个容器中-->
<FrameLayout
android:id="@+id/fl_leapmotor_status_bar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.StatusBarWindowView>
以上代码和布局文件主要处理了以下几件事情:
- 由于系统原生的StatusBar中内容过多,为了让自定义代码和系统原生代码进行解耦,这里我们创建了新的状态栏类LeapMotorCarStatusBar。
- 关于super_status_bar.xml布局,我们隐藏该布局中原有的状态栏视图内容,在最底部添加了新的id为fl_leapmotor_status_bar存放自定义状态栏视图的容器FrameLayout。
- 重写StatusBar的inflateStatusBarWindow方法,在该方法中创建LeapMotorStatusBarViewHelper对象,并将该对象的视图内容添加到id为fl_leapmotor_status_bar的FrameLayout中。
接下来我们继续来看下自定义布局对象容器LeapMotorStatusBarViewHelper 的关键代码。
3、编写自定义状态栏视图布局和交互逻辑:
frameworks/base/packages/SystemUI/src/com/leapmotor/systemui/LeapMotorStatusBarViewHelper.java
public class LeapMotorStatusBarViewHelper {
protected Context mContext;
protected View mRootView;
public LeapMotorStatusBarViewHelper(Context mContext) {
this.mContext = mContext;
mRootView = LayoutInflater.from(mContext).inflate(R.layout.leapmotor_layout_status_bar_view, null, false);
}
public View getRootView() {
return mRootView;
}
}
- LeapMotorStatusBarViewHelper这个类很关键,自定义状态栏相关的交互逻辑基本都可以写在这里。
- leapmotor_layout_status_bar_view.xml是自定义状态栏所对应的布局文件。
通过修改LeapMotorStatusBarViewHelper和leapmotor_layout_status_bar_view.xml的内容,基本就能实现定制系统状态栏的功能了。
三、定制导航栏
1、LeapMotorCarStatusBar
frameworks/base/packages/SystemUI/src/com/leapmotor/systemui/LeapMotorCarStatusBar .java
public class LeapMotorCarStatusBar extends StatusBar{
//重写StatusBar的createNavigationBar方法,创建自定义底部导航栏视图对象。
@Override
protected void createNavigationBar() {
mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
if (mShowBottom) {
mLeapMotorNaviBarViewHelper = new LeapMotorNaviBarViewHelper(mStatusBarWindow.getContext());
}
attachNavBarWindows();
}
//将导航栏添加到Window中
private void attachNavBarWindows() {
if (mShowBottom && mLeapMotorDockBarViewHelper != null) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
lp.setTitle("LeapMotorNavigationBar");
lp.windowAnimations = 0;
mWindowManager.addView(mLeapMotorNaviBarViewHelper.getRootView(), lp);
}
}
}
继续在LeapMotorCarStatusBar中重写StatusBar的createNavigationBar方法,在该方法中创建LeapMotorNaviBarViewHelper对象,并将该对象的视图内容添加到WindowManager中,这样原生的系统导航栏就会被我们替换掉。至于自定义导航栏关键类LeapMotorNaviBarViewHelper的相关代码,请参考上面的自定义状态栏关键类LeapMotorStatusBarViewHelper的代码,这里不再继续讲述。
四、禁用下拉状态栏和通知栏
在成功定制系统状态栏和导航栏之后,大部分情况下,我们可能还需要将原生的下拉通知栏禁止掉,想要实现这个功能,
可以修改KeyguardViewMediator这个类的adjustStatusBarLocked方法,在该方法的最后将flags设置为StatusBarManager.DISABLE_EXPAND即可。
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) {
if (mStatusBarManager == null) {
mStatusBarManager = (StatusBarManager)
mContext.getSystemService(Context.STATUS_BAR_SERVICE);
}
if (mStatusBarManager == null) {
Log.w(TAG, "Could not get status bar manager");
} else {
int flags = StatusBarManager.DISABLE_NONE;
if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
flags |= StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT;
}
//add by af
flags = StatusBarManager.DISABLE_EXPAND;
//add by af
mStatusBarManager.disable(flags);
}
}
五、调整状态栏和导航栏的高度
状态栏和导航栏高度的最初取值都来源于frameworks/base/core/res/res/values/dimens.xml文件中的配置字段:
frameworks/base/core/res/res/values/dimens.xml
- 状态栏高度
<!-- Height of the status bar -->
<dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
<!-- Height of the status bar in portrait -->
<dimen name="status_bar_height_portrait">24dp</dimen>
<!-- Height of the status bar in landscape -->
<dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>
- 导航栏高度
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
<dimen name="navigation_bar_height_landscape">48dp</dimen>
<!-- Height of the bottom navigation / system bar in car mode. -->
<dimen name="navigation_bar_height_car_mode">96dp</dimen>
- 如果修改了以上相关字段之后,状态栏高度和导航栏高度还是没有发生变化,那么就要去看一下和frameworkd同级的package文件夹中是否存在status_bar_height和navigation_bar_height字段,在进行系统编译的时候,/package/目录中的某些子目录的相关配置字段会覆盖掉frameworks/子目录里面同名的字段。