Android 9.0禁止下拉状态栏和通知栏,自定义状态栏和导航栏视图

一、状态栏组件的启动流程

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/子目录里面同名的字段。

版权声明:本文为abc6368765原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。