Android 9.0系统源码_SystemUI(七)自定义状态栏和导航栏视图

邹嘉致
2023-12-01

前言

前面几篇文章我们具体分析了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>

以上代码和布局文件主要处理了以下几件事情:

  • 由于系统原生的StatusBar中内容过多,为了让自定义代码和系统原生代码进行解耦,这里我们创建了新的状态栏类LPCarStatusBar。
  • 关于super_status_bar.xml布局,我们隐藏该布局中原有的状态栏视图内容,在最底部添加了新的id为fl_lp_status_bar存放自定义状态栏视图的容器FrameLayout。
  • 重写StatusBar的inflateStatusBarWindow方法,在该方法中创建LPStatusBarViewHelper对象,并将该对象的视图内容添加到id为fl_lp_status_bar的FrameLayout中。

接下来我们继续来看下自定义布局对象容器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是自定义状态栏所对应的布局文件。

通过修改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>
  • 如果修改了以上相关字段之后,状态栏高度和导航栏高度还是没有发生变化,那么就要去看一下和frameworkd同级的package文件夹中是否存在status_bar_height和navigation_bar_height字段,在进行系统编译的时候,/package/目录中的某些子目录的相关配置字段会覆盖掉frameworks/子目录里面同名的字段。

六、为状态栏和导航栏设置透明背景

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属性字段,状态栏和导航栏背景的默认颜色就会变成透明颜色。

 类似资料: