前面几篇文章我们具体分析了Android9.0系统原生的SystemUI模块各个组件的启动流程和视图构建流程,特别是StatusBar;本篇文章我们将会在StatusBar的基础上构建自定义状态栏和导航栏视图。
1、首先再来梳理一下SystemUI组件的启动流程,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.lp.systemui.LPCarStatusBar</string>
在修改这个字段以后,Android系统开机之后,便会启动LPCarStatusBar 这个类。
2、新建继承自StatusBar的状态栏类LPCarStatusBar
frameworks/base/packages/SystemUI/src/com/lp/systemui/LPCarStatusBar.java
public class LPCarStatusBar extends StatusBar{
//重写StatusBar的inflateStatusBarWindow方法,创建自定义状态栏视图对象。
@Override
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
FrameLayout flStatusBar = mStatusBarWindow.findViewById(R.id.fl_lp_status_bar);
LPStatusBarViewHelper statusBarHelper = new LPStatusBarViewHelper(mStatusBarWindow.getContext());
flStatusBar.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_lp_status_bar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.StatusBarWindowView>
以上代码和布局文件主要处理了以下几件事情:
接下来我们继续来看下自定义布局对象容器LPStatusBarViewHelper的关键代码。
3、下面我们开始来编写自定义状态栏视图布局和交互逻辑。
frameworks/base/packages/SystemUI/src/com/lp/systemui/LPStatusBarViewHelper.java
public class LPStatusBarViewHelper{
protected Context mContext;
protected View mRootView;
public LPStatusBarViewHelper(Context mContext) {
this.mContext = mContext;
mRootView = LayoutInflater.from(mContext).inflate(R.layout.lp_layout_status_bar_view, null, false);
}
public View getRootView() {
return mRootView;
}
}
通过修改LPStatusBarViewHelper和lp_layout_status_bar_view.xml的内容,基本就能实现定制系统状态栏的功能了。
1、继续在LPCarStatusBar类中重写createNavigationBar方法,并在该方法中构建自定义导航栏视图。
frameworks/base/packages/SystemUI/src/com/lp/systemui/LPCarStatusBar .java
public class LPCarStatusBar extends StatusBar{
//重写StatusBar的createNavigationBar方法,创建自定义底部导航栏视图对象。
@Override
protected void createNavigationBar() {
mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
if (mShowBottom) {
mLPNaviBarViewHelper = new LPNaviBarViewHelper(mStatusBarWindow.getContext());
}
attachNavBarWindows();
}
//将导航栏添加到Window中
private void attachNavBarWindows() {
if (mShowBottom && mLPNaviBarViewHelper != 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("LPNaviBarViewHelper");
lp.windowAnimations = 0;
mWindowManager.addView(mLPNaviBarViewHelper.getRootView(), lp);
}
}
}
继续在LPCarStatusBar中重写StatusBar的createNavigationBar方法,在该方法中创建LPNaviBarViewHelper对象,并将该对象的视图内容添加到WindowManager中,这样原生的系统导航栏就会被我们替换掉。至于自定义导航栏关键类LPNaviBarViewHelper的相关代码,请参考上面的自定义状态栏关键类LPStatusBarViewHelper的代码,这里不再继续讲述。
在成功定制系统状态栏和导航栏之后,大部分情况下,我们可能还需要将原生的下拉通知栏禁止掉,想要实现这个功能,
可以修改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>
1、状态栏和导航栏颜色的取值和PhoneWindow的generateLayout方法中的一段代码有关联:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
int mStatusBarColor = 0;//状态栏背景颜色
int mNavigationBarColor = 0;//导航栏背景颜色
int mNavigationBarDividerColor = 0;//导航栏分割线颜色
protected ViewGroup generateLayout(DecorView decor) {
...代码省略...
//获取状态栏颜色,如果样式中不存在statusBarColor字段,则默认为黑色
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
...代码省略...
//获取导航栏颜色,如果样式中不存在navigationBarColor字段,则默认为黑色
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
//获取导航栏分割线颜色,如果样式中不存在navigationBarDividerColor字段,则分割线默认为透明颜色
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor, 0x00000000);
}
}
从上面的代码可以知道状态栏和导航栏的背景颜色都是从样式中获取的,如果样式中没有,则默认都为黑色背景,而导航栏分割线默认为透明颜色。
2、我们可以比照导航栏分割线,也修改状态栏和导航栏默认颜色取值为透明色:
//获取状态栏颜色,如果样式中不存在statusBarColor字段,则默认为透明颜色
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0x00000000);
...代码省略...
//获取导航栏颜色,如果样式中不存在navigationBarColor字段,则默认为透明颜色
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0x00000000);
在我们将状态栏背景和导航栏背景的默认取值0xFF000000改成0x00000000之后,当样式中不存在statusBarColor属性时,状态栏默认为透明色,当样式中不存在navigationBarColor属性时,导航栏默认为透明色。在修改完代码之后,我们还需要去除掉系统源码中的statusBarColor属性字段和navigationBarColor属性字段。
3、系统中默认的statusBarColor和navigationBarColor如下所示:
framework/base/core/res/res/values/themes.xml
<style name="Theme">
...代码省略...
<item name="statusBarColor">@color/black</item>
<item name="navigationBarColor">@color/black</item>
..代码省略...
</style>
framework/base/core/res/res/values/themes_material.xml
<style name="Theme.Material">
...代码省略...
<item name="statusBarColor">?attr/colorPrimaryDark</item>
<item name="navigationBarColor">@color/black</item>
..代码省略...
</style>
<style name="Theme.Material.Light" parent="Theme.Light">
...代码省略...
<item name="statusBarColor">?attr/colorPrimaryDark</item>
<item name="navigationBarColor">@color/black</item>
..代码省略...
</style>
从上面的代码可以知道:
1)如果应用的样式继承自Theme,则状态栏和导航栏背景的默认颜色为黑色的,
2)如果应用的样式继承自Theme.Material或Theme.Material.Light,则状态栏背景的默认颜色取自colorPrimaryDark属性,导航栏背景的默认颜色为黑色。
4、如果我们想让前面第2步在PhoneWindow中修改的代码
//获取状态栏颜色,如果样式中不存在statusBarColor字段,则默认为透明颜色
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0x00000000);
...代码省略...
//获取导航栏颜色,如果样式中不存在navigationBarColor字段,则默认为透明颜色
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0x00000000);
生效,只需要将第3步样式中的statusBarColor和navigationBarColor属性去除或者设置为transparent即可。
framework/base/core/res/res/values/themes.xml
<style name="Theme">
...代码省略...
<item name="statusBarColor">@color/transparent</item>
<item name="navigationBarColor">@color/transparent</item>
..代码省略...
</style>
framework/base/core/res/res/values/themes_material.xml
<style name="Theme.Material">
...代码省略...
<item name="statusBarColor">@color/transparent</item>
<item name="navigationBarColor">@color/transparent</item>
..代码省略...
</style>
<style name="Theme.Material.Light" parent="Theme.Light">
...代码省略...
<item name="statusBarColor">@color/transparent</item>
<item name="navigationBarColor">@color/transparent</item>
..代码省略...
</style>
这样只要应用所使用的样式中没有添加自定义的statusBarColor和navigationBarColor属性字段,状态栏和导航栏背景的默认颜色就会变成透明颜色。