1、需求
屏蔽下拉状态栏谷歌录屏、省电模式、二维码扫描器等快捷按钮。
2、修改路径
普及:安卓的SystemUI包提供了状态栏、导航栏、通知中心等重要的用户界面元素。
状态栏小部件UI显示修改路径:frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
3、修改思路
下拉状态栏属于系统UI的一部分,位于SystemUI包下,不同安卓版本这个包的代码有些许出入,但是万变不离其宗,掌握修改原理即可,下面来一步步分析如何修改以及为什么要这样修改:
(1)修改方法
打开这个文件:frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java,定位到这个方法:
这个方法做了两件事,第一件事是加载“存货”(Stock tiles),什么是存货呢,就是安卓原生自带的小部件,比如WiFi、蓝牙、定位等快捷按钮,第二件事是加载工程师自定义的小部件(Custom tiles),在这里我们先不研究自定义的小部件,以后有时间再研究。
既然是加载“存货”,那这些存货从哪里来呢,先不看这个,我先把修改代码放出来,若不想研究原理可不看后面,修改代码如下:
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
if (mTileMap.containsKey(tileSpec)
// We should not return a Garbage Monitory Tile if the build is not Debuggable
&& (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) {
//屏蔽下拉状态栏中的几个tile,返回空对象
Set<String> multipleStrings = new HashSet<>();
multipleStrings.add("screenrecord"); //谷歌录屏
multipleStrings.add("battery"); // 省电模式
multipleStrings.add("qr_code_scanner"); // 二维码扫描器
if (multipleStrings.contains(tileSpec)) {
return null;
}
return mTileMap.get(tileSpec).get();
}
// Custom tiles
if (tileSpec.startsWith(CustomTile.PREFIX)) {
return CustomTile.create(
mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
// Broken tiles.
Log.w(TAG, "No stock tile spec: " + tileSpec);
return null;
}
好了,结束!
nonono,开个玩笑,接下来讲解原理,因为有些版本不是这样改的,待会就知道了。
(2)修改原理
首先我们知道,谷歌的代码写的都是很优雅的,想完全看懂不容易,这里我们只啃一部分,那就是下拉状态栏那些快捷按钮(QS tile)是如何添加的。
首先我们观察SystemUI目录,里面有很多子目录,大部分都是每一个模块细化出来,比如WiFi相关UI放在一个目录,声音UI放一个目录,目录里面基本都是写小模块的具体实现。
而qs目录就是专门负责实现通知栏或状态栏这个面板的各种功能和交互效果,比如图标的显示、动画效果、点击事件的处理等。
这里我们主要看qs目录,该目录结构如图:
其中,QSContainerImpl.java 这个类作为下拉状态栏快速面板的布局承载各种View,QSTileHost.java类是个接口实现类,里面实现了对tile的增删改查操作:
例如,我们想移除谷歌相机,同样可以在布局初始化处调用removeTile方法:
@Override
public void removeTile(String spec) {
if (spec.startsWith(CustomTile.PREFIX)) {
// If the tile is removed (due to it not actually existing), mark it as removed. That
// way it will be marked as newly added if it appears in the future.
setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
}
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
传入一个字符串“screenrecord”,关于怎么知道各个组件的键值,后面会讲。
注意,调用这个方法只能移除原生自带的一些组件键值,addTile(String spec) 这个方法也是只能创建自带的组件。
接下来回到上面修改的实现类QSFactoryImpl.java里面看它是如何移除谷歌相机的,首先,这个mTileMap是一个map 集合,里面加载了所有的谷歌原生tile键值
我们看下这个map 是从哪里传递过来的,
看到是通过构造方法传递,继续找哪里调用这个构造方法
在这个类里面,我们看到tileMap在这里传递了进来,看一下tileMap的结构
val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>(
"internet" to Provider { internetTile },
"bt" to Provider { bluetoothTile },
"dnd" to Provider { dndTile },
"inversion" to Provider { colorInversionTile },
"airplane" to Provider { airplaneTile },
"work" to Provider { workTile },
"rotation" to Provider { rotationTile },
"flashlight" to Provider { flashlightTile },
"location" to Provider { locationTile },
"cast" to Provider { castTile },
"hotspot" to Provider { hotspotTile },
"battery" to Provider { batterySaverTile },
"saver" to Provider { dataSaverTile },
"night" to Provider { nightDisplayTile },
"nfc" to Provider { nfcTile },
"dark" to Provider { darkModeTile },
"screenrecord" to Provider { screenRecordTile },
"reduce_brightness" to Provider { reduceBrightColorsTile },
"cameratoggle" to Provider { cameraToggleTile },
"mictoggle" to Provider { microphoneToggleTile },
"controls" to Provider { deviceControlsTile },
"alarm" to Provider { alarmTile },
"wallet" to Provider { quickAccessWalletTile },
"qr_code_scanner" to Provider { qrCodeScannerTile },
"onehanded" to Provider { oneHandedModeTile },
"color_correction" to Provider { colorCorrectionTile },
"dream" to Provider { dreamTile },
"font_scaling" to Provider { fontScalingTile }
)
这段代码是 Kotlin 语言编写的,它定义了一个名为 tileMap 的可变映射(MutableMap)。这个映射的键是字符串类型(String),而值是 Provider<QSTileImpl<*>> 类型。Provider 是 Dagger 框架中的一个接口,它代表了一个对象的延迟提供者,即当需要时才会创建该对象。以**“screenrecord” to Provider { screenRecordTile },**为例,这个screenrecord键值映射了一个provider,当请求screenrecord键时,会返回一个screenRecordTile对象,这个对象就是谷歌录屏这个按钮的实体,实现了谷歌录屏这个快捷按钮的一些行为,比如点击录制,停止录制等。
我们回到创建过程看
在创建过程时加入过滤集合,将不需要显示的快捷按钮的键值加到set中,如果集合包含要创建的tile,就返回一个空对象,上面对对象判空,如果为空,则不会创建tile。
明白了创建原理,改起来就很简单了,比如在安卓其他版本,这个实现类并不是这样写的
这里这个实现类是直接在当前类根据tile标识返回对应的实体
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
switch (tileSpec) {
case "wifi":
return mWifiTileProvider.get();
case "internet":
return mInternetTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
/*case "cell":
return mCellularTileProvider.get();*/
case "dnd":
return mDndTileProvider.get();
case "inversion":
return mColorInversionTileProvider.get();
case "airplane":
return mAirplaneModeTileProvider.get();
/*case "work":
return mWorkModeTileProvider.get();
case "rotation":
return mRotationLockTileProvider.get();
case "flashlight":
return mFlashlightTileProvider.get();
case "location":
return mLocationTileProvider.get();*/
case "cast":
return mCastTileProvider.get();
case "hotspot":
return mHotspotTileProvider.get();
/*case "battery":
return mBatterySaverTileProvider.get();
case "saver":
return mDataSaverTileProvider.get();
case "night":
return mNightDisplayTileProvider.get();
case "nfc":
return mNfcTileProvider.get();
case "dark":
return mUiModeNightTileProvider.get();*/
/*case "screenrecord":
return mScreenRecordTileProvider.get();*/
/*case "reduce_brightness":
return mReduceBrightColorsTileProvider.get();
case "cameratoggle":
return mCameraToggleTileProvider.get();
case "mictoggle":
return mMicrophoneToggleTileProvider.get();*/
case "controls":
return mDeviceControlsTileProvider.get();
/*case "alarm":
return mAlarmTileProvider.get();
case "wallet":
return mQuickAccessWalletTileProvider.get();
case "qr_code_scanner":
return mQRCodeScannerTileProvider.get();
case "onehanded":
return mOneHandedModeTileProvider.get();*/
case "color_correction":
return mColorCorrectionTileProvider.get();
/*case "dream":
return mDreamTileProvider.get();*/
}
// Custom tiles
if (tileSpec.startsWith(CustomTile.PREFIX)) {
return CustomTile.create(
mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
// Debug tiles.
/*if (Build.IS_DEBUGGABLE) {
if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) {
return mMemoryTileProvider.get();
}
}*/
// Broken tiles.
Log.w(TAG, "No stock tile spec: " + tileSpec);
return null;
}
这种也很简单,直接注释掉不要的tile即可,这个版本的实现类写的稍微就没那么优雅了,但是基本原理都一样。
好了,先写到这里。
3、总结
系统下拉状态栏包含的内容还是很多的,目前只分析了如何移除快捷按钮,继续刨析还可以实现增加自定义的按钮,不过得遵循谷歌的一套做法,再深入了解还可以美化状态栏,这些就留给以后去慢慢分析了。
共勉:分享知识,共同进步!