概述
本文续自:Android 11 的状态栏的隐藏
PS
本文虽然同属于SystemUI, 但目前并 没有 打算整理成专橍或撰写一个系列的想法.
仅仅为了记录一些过程, 留下那些容易被遗忘的点滴.
开始下拉时状态栏图标被隐藏
状态橍的图标在用户开始触摸(ACTION_DOWN)后, 会开始展开, 显示扩展面板, 同时, 隐藏状态橍上的通知和状态图标. 在手机上表现有可能不同, 在android 13上, 在点击没有作用, 只有下拉一定的距离,才会开始隐藏.
device-2022-12-21-190046
隐藏从触摸下按开始, 参照下图:
忽略过程代码, 直接上最后一部分:
frameworks/base/packages/SystemUI//src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@Override public void disable(int displayId, int state1, int state2, boolean animate) { if (displayId != getContext().getDisplayId()) { return; } state1 = adjustDisableFlags(state1); final int old1 = mDisabled1; final int diff1 = state1 ^ old1; mDisabled1 = state1; if ((diff1 & DISABLE_SYSTEM_INFO) != 0) { if ((state1 & DISABLE_SYSTEM_INFO) != 0) { hideSystemIconArea(animate); hideOperatorName(animate); } else { showSystemIconArea(animate); showOperatorName(animate); } } if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) { if ((state1 & DISABLE_NOTIFICATION_ICONS) != 0) { hideNotificationIconArea(animate); } else { showNotificationIconArea(animate); } } // The clock may have already been hidden, but we might want to shift its // visibility to GONE from INVISIBLE or vice versa if ((diff1 & DISABLE_CLOCK) != 0 || mClockView.getVisibility() != clockHiddenMode()) { if ((state1 & DISABLE_CLOCK) != 0) { hideClock(animate); } else { showClock(animate); } } } protected int adjustDisableFlags(int state) { boolean headsUpVisible = mStatusBarComponent.headsUpShouldBeVisible(); if (headsUpVisible) { state |= DISABLE_CLOCK; } if (!mKeyguardStateController.isLaunchTransitionFadingAway() && !mKeyguardStateController.isKeyguardFadingAway() && shouldHideNotificationIcons() && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD && headsUpVisible)) { state |= DISABLE_NOTIFICATION_ICONS; state |= DISABLE_SYSTEM_INFO; state |= DISABLE_CLOCK; } if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) { if (mNetworkController.hasEmergencyCryptKeeperText()) { state |= DISABLE_NOTIFICATION_ICONS; } if (!mNetworkController.isRadioOn()) { state |= DISABLE_SYSTEM_INFO; } } // The shelf will be hidden when dozing with a custom clock, we must show notification // icons in this occasion. if (mStatusBarStateController.isDozing() && mStatusBarComponent.getPanelController().hasCustomClock()) { state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; } return state; }
关于CollapsedStatusBarFragment
在读到CollapsedStatusBarFragment相关代码的时候, 一时没办法把Fragment与SystemUI串联起来, 这源于传统Fragment的使用习惯: 在Activity中, 获取一个FragmentManager, 创建各种Fragment, 调用replace, show, hide…
难道SystemUI也吃这一套? 状态栏和导航栏不一直是通过WindowManager.addView()直接往里面丢的么!
CollapsedStatusBarFragment 本身没什么特别的, 它只是一个android.app.Fragment, 没有特殊的地方, 特殊的是FragmentController, 它与传统的Fragment使用方法完全不同, 参考 frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
编写一段测试代码, 以便更直观了解它的用法:
public class TestActivity extends Activity{ void testFragmentController(Context ctx, Handler h, int winAnim){ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { FragmentController fragCtrl = FragmentController.createController(new FragmentHostCallback<Object>(ctx, h, 0) { @Override public Object onGetHost() { return null; } @Override public <T extends View> T onFindViewById(int id) { return findViewById(id); } }); //java.lang.IllegalStateException: Activity has been destroyed // at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1913) fragCtrl.attachHost(null); fragCtrl.dispatchCreate(); fragCtrl.dispatchStart(); fragCtrl.dispatchResume(); //java.lang.IllegalArgumentException: No view found for id 0x7f08007c (com.android.factorytest:id/flRoot) for fragment SimpleFragment{2b5da47 #0 id=0x7f08007c} fragCtrl.getFragmentManager() .beginTransaction() .replace(R.id.flRoot, new SimpleFragment()) .commit(); } } public static class SimpleFragment extends Fragment{ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setLayoutParams(new ViewGroup.LayoutParams(UiTools.MATCH_PARENT, UiTools.MATCH_PARENT)); tv.setText("FragmentController"); tv.setTextSize(36); return tv; } } }
重点关注代码中的createController和FragmentHostCallback, 当创建完成后, 便可以通过 FragmentController的getFragmentManager获取FragmentManager, 接下来就是熟悉的操作, 不多描述.
下拉通知黑色背景
在下拉通知面板的过程中, 存在两部分的半透明背景, 第一部分(上)比较明显, 第二部分比较隐藏晦.
效果如下图:
这是一个自定义的View, 从layout文件中可以找到它: ScrimView
frameworks/base/packages/SystemUI/res/layout/super_notification_shade.xml
<com.android.systemui.statusbar.phone.NotificationShadeWindowView 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.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" sysui:ignoreRightInset="true" />
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
diff --git a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index 7f30009cda..907d58c267 100644 --- a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java @@ -69,7 +69,7 @@ public class ScrimView extends View { @Override protected void onDraw(Canvas canvas) { - if (mDrawable.getAlpha() > 0) { + if (false && mDrawable.getAlpha() > 0) {//强制不进行绘制, 就不会出现半透明背景 mDrawable.draw(canvas); } }
第二部分只有在下拉到底部时才会出现(不仔细分辨很难看出来):
同样, 来自另一个自定义控件:AlphaOptimizedView
frameworks\base\packages\SystemUI\res\layout\status_bar_expanded.xml
<com.android.systemui.statusbar.AlphaOptimizedView android:id="@+id/qs_navbar_scrim" android:layout_height="66dp" android:layout_width="match_parent" android:layout_gravity="bottom" android:visibility="invisible" android:background="@drawable/qs_navbar_scrim" />
扩展
Dagger, 绕得脑壳疼, 记录下out下的路径
out/soong/.intermediates/frameworks/base/packages/SystemUI/SystemUI-core/android_common/kapt/gen/sources/com/android/systemui/dagger/DaggerSystemUIRootComponent.java
public final class DaggerSystemUIRootComponent implements SystemUIRootComponent { private Provider<SystemUIRootComponent> systemUIRootComponentProvider; this.systemUIRootComponentProvider = InstanceFactory.create((SystemUIRootComponent) this); private Provider<FragmentService> fragmentServiceProvider; this.fragmentServiceProvider = DoubleCheck.provider( FragmentService_Factory.create( systemUIRootComponentProvider, provideConfigurationControllerProvider)); }
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@NonNull @Override public Application instantiateApplicationCompat( @NonNull ClassLoader cl, @NonNull String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = super.instantiateApplicationCompat(cl, className); if (app instanceof ContextInitializer) { ((ContextInitializer) app).setContextAvailableCallback( context -> { SystemUIFactory.createFromConfig(context); SystemUIFactory.getInstance().getRootComponent().inject( SystemUIAppComponentFactory.this); } ); } return app; }
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
private void init(Context context) { mRootComponent = buildSystemUIRootComponent(context); // Every other part of our codebase currently relies on Dependency, so we // really need to ensure the Dependency gets initialized early on. Dependency dependency = new Dependency(); mRootComponent.createDependency().createSystemUI(dependency); dependency.start(); } public static void createFromConfig(Context context) { if (mFactory != null) { return; } final String clsName = context.getString(R.string.config_systemUIFactoryComponent); if (clsName == null || clsName.length() == 0) { throw new RuntimeException("No SystemUIFactory component configured"); } try { Class<?> cls = null; cls = context.getClassLoader().loadClass(clsName); mFactory = (SystemUIFactory) cls.newInstance(); mFactory.init(context); } catch (Throwable t) { Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t); throw new RuntimeException(t); } }
frameworks/base/packages/SystemUI/src/com/android/systemui/Dependency.java
@Inject Lazy<FragmentService> mFragmentService; protected void start() { mProviders.put(FragmentService.class, mFragmentService::get); }
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
FragmentHostManager.get(mPhoneStatusBarWindow)
frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
public static FragmentHostManager get(View view) { try { return Dependency.get(FragmentService.class).getFragmentHostManager(view); } catch (ClassCastException e) { // TODO: Some auto handling here? throw e; } }
frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
public FragmentHostManager getFragmentHostManager(View view) { View root = view.getRootView(); FragmentHostState state = mHosts.get(root); if (state == null) { state = new FragmentHostState(root); mHosts.put(root, state); } return state.getFragmentHostManager(); }
参考
SystemUI源码分析之PhoneStatusBar初始化布局简单分析
Android SystemUI 状态栏网络图标显示分析(Android 11)
SystemUI之状态图标控制
Android 8.0 SystemUI(三):一说顶部 StatusBar
Android 8.0 SystemUI(四):二说顶部 StatusBar
Dagger 基础知识
在 Android 应用中使用 Dagger