Android 9.0 SystemUI 下拉状态栏快捷开关
SystemUI 下拉状态栏快捷开关是 QSPanel,qs_panel.xml,@+id/quick_settings_panel,本篇文章就来看看这些快捷开关是如何呈现的以及如何新增一个快捷开关?基于 AOSP 9.0 分析。
SystemUI 下拉状态栏快捷开关
QSPanel 创建是从 StatusBar#makeStatusBarView 开始的。
StatusBar#makeStatusBarView
protected void makeStatusBarView() {//省略其他代码final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,mIconController);mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,(visible) -> {mBrightnessMirrorVisible = visible;updateScrimController();});fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {QS qs = (QS) f;if (qs instanceof QSFragment) {((QSFragment) qs).setHost(qsh);mQSPanel = ((QSFragment) qs).getQsPanel();mQSPanel.setBrightnessMirror(mBrightnessMirrorController);mKeyguardStatusBar.setQSPanel(mQSPanel);}});//省略其他代码
}
复制
先看 SystemUIFactory#createQSTileHost。
SystemUIFactory#createQSTileHost
public QSTileHost createQSTileHost(Context context, StatusBar statusBar,StatusBarIconController iconController) {return new QSTileHost(context, statusBar, iconController);
}
复制
这里进行 QSTileHost 初始化。
QSTileHost#构造函数
public QSTileHost(Context context, StatusBar statusBar,StatusBarIconController iconController) {//省略其他代码Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);//省略其他代码
}
复制
这里进行了 TunerService 注册,在 TunerServiceImpl#addTunable 重写。
TunerServiceImpl#addTunable
@Override
public void addTunable(Tunable tunable, String... keys) {for (String key : keys) {addTunable(tunable, key);}
}
private void addTunable(Tunable tunable, String key) {if (!mTunableLookup.containsKey(key)) {mTunableLookup.put(key, new ArraySet<Tunable>());}mTunableLookup.get(key).add(tunable);if (LeakDetector.ENABLED) {mTunables.add(tunable);Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");}Uri uri = Settings.Secure.getUriFor(key);if (!mListeningUris.containsKey(uri)) {mListeningUris.put(uri, key);mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);}// Send the first state.String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);tunable.onTuningChanged(key, value);
}
复制
tunable.onTuningChanged
回调 QSTileHost#onTuningChanged。
QSTileHost#onTuningChanged
@Override
public void onTuningChanged(String key, String newValue) {if (!TILES_SETTING.equals(key)) {return;}if (DEBUG) Log.d(TAG, "Recreating tiles");if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);}//调用 QSTileHost#loadTileSpecs,获得 config 里字符串信息final List<String> tileSpecs = loadTileSpecs(mContext, newValue);int currentUser = ActivityManager.getCurrentUser();if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;//进行了过滤mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(tile -> {if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());tile.getValue().destroy();});final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();for (String tileSpec : tileSpecs) {QSTile tile = mTiles.get(tileSpec);if (tile != null && (!(tile instanceof CustomTile)|| ((CustomTile) tile).getUser() == currentUser)) {if (tile.isAvailable()) {if (DEBUG) Log.d(TAG, "Adding " + tile);tile.removeCallbacks();if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {tile.userSwitch(currentUser);}newTiles.put(tileSpec, tile);} else {tile.destroy();}} else {if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);try {//这里通过 字符串 一个个实例化 Tiletile = createTile(tileSpec);if (tile != null) {if (tile.isAvailable()) {tile.setTileSpec(tileSpec);newTiles.put(tileSpec, tile);} else {tile.destroy();}}} catch (Throwable t) {Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);}}}mCurrentUser = currentUser;mTileSpecs.clear();mTileSpecs.addAll(tileSpecs);mTiles.clear();mTiles.putAll(newTiles);for (int i = 0; i < mCallbacks.size(); i++) {//注册,当开发状态改变时回调mCallbacks.get(i).onTilesChanged();}
}
复制
看下 QSTileHost#loadTileSpecs,是获得 config 里字符串信息。
QSTileHost#loadTileSpecs
protected List<String> loadTileSpecs(Context context, String tileList) {final Resources res = context.getResources();final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);if (tileList == null) {tileList = res.getString(R.string.quick_settings_tiles);if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);} else {if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);}final ArrayList<String> tiles = new ArrayList<String>();boolean addedDefault = false;for (String tile : tileList.split(",")) {tile = tile.trim();if (tile.isEmpty()) continue;if (tile.equals("default")) {if (!addedDefault) {tiles.addAll(Arrays.asList(defaultTileList.split(",")));addedDefault = true;}} else {tiles.add(tile);}}return tiles;
}
复制
其中 quick_settings_tiles_default 值在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里:
<string name="quick_settings_tiles_default" translatable="false">wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
</string>
复制
这里就是我们所看到的快捷开关的文本描述。
再看 QSTileHost#onTuningChanged 中的调用 QSTileHost#createTile 方法。
QSTileHost#createTile
public QSTile createTile(String tileSpec) {for (int i = 0; i < mQsFactories.size(); i++) {QSTile t = mQsFactories.get(i).createTile(tileSpec);if (t != null) {return t;}}return null;
}
复制
调用 QSFactory#createTile,由 QSFactoryImpl#createTile 实现了。
QSFactoryImpl#createTile
public QSTile createTile(String tileSpec) {QSTileImpl tile = createTileInternal(tileSpec);if (tile != null) {tile.handleStale(); // Tile was just created, must be stale.}return tile;
}
private QSTileImpl createTileInternal(String tileSpec) {// Stock tiles.switch (tileSpec) {case "wifi":return new WifiTile(mHost);case "bt":return new BluetoothTile(mHost);case "cell":return new CellularTile(mHost);case "dnd":return new DndTile(mHost);case "inversion":return new ColorInversionTile(mHost);case "airplane":return new AirplaneModeTile(mHost);case "work":return new WorkModeTile(mHost);case "rotation":return new RotationLockTile(mHost);case "flashlight":return new FlashlightTile(mHost);case "location":return new LocationTile(mHost);case "cast":return new CastTile(mHost);case "hotspot":return new HotspotTile(mHost);case "user":return new UserTile(mHost);case "battery":return new BatterySaverTile(mHost);case "saver":return new DataSaverTile(mHost);case "night":return new NightDisplayTile(mHost);case "nfc":return new NfcTile(mHost);}// Intent tiles.if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);// Debug tiles.if (Build.IS_DEBUGGABLE) {if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) {return new GarbageMonitor.MemoryTile(mHost);}}// Broken tiles.Log.w(TAG, "Bad tile spec: " + tileSpec);return null;
}
复制
看到这里通过对应的字符串分别实例化 Tile。
以上涉及资源文件加载及对应实例化,接下来看看代码如何加载的,看 QSPanel#onAttachedToWindow 方法。
QSPanel#onAttachedToWindow
@Override
protected void onAttachedToWindow() {super.onAttachedToWindow();final TunerService tunerService = Dependency.get(TunerService.class);tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);if (mHost != null) {setTiles(mHost.getTiles());}if (mBrightnessMirrorController != null) {mBrightnessMirrorController.addCallback(this);}
}
public void setTiles(Collection<QSTile> tiles) {setTiles(tiles, false);
}
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {if (!collapsedView) {mQsTileRevealController.updateRevealedTiles(tiles);}for (TileRecord record : mRecords) {mTileLayout.removeTile(record);record.tile.removeCallback(record.callback);}mRecords.clear();for (QSTile tile : tiles) {addTile(tile, collapsedView);}
}
protected TileRecord addTile(final QSTile tile, boolean collapsedView) {final TileRecord r = new TileRecord();r.tile = tile;r.tileView = createTileView(tile, collapsedView);//省略其他代码r.tileView.init(r.tile);r.tile.refreshState();mRecords.add(r);if (mTileLayout != null) {mTileLayout.addTile(r);}return r;
}
复制
mTileLayout.addTile(r);由 PagedTileLayout#addTile 实现。
PagedTileLayout#addTile
PagedTileLayout 是 ViewPager,重点看 setAdapter,看数据源如何 add 的。
@Override
public void addTile(TileRecord tile) {mTiles.add(tile);postDistributeTiles();
}
private void postDistributeTiles() {removeCallbacks(mDistribute);post(mDistribute);
}
private final Runnable mDistribute = new Runnable() {@Overridepublic void run() {distributeTiles();}
};
private void distributeTiles() {if (DEBUG) Log.d(TAG, "Distributing tiles");final int NP = mPages.size();for (int i = 0; i < NP; i++) {mPages.get(i).removeAllViews();}int index = 0;final int NT = mTiles.size();for (int i = 0; i < NT; i++) {TileRecord tile = mTiles.get(i);if (mPages.get(index).isFull()) {if (++index == mPages.size()) {if (DEBUG) Log.d(TAG, "Adding page for "+ tile.tile.getClass().getSimpleName());mPages.add((TilePage) LayoutInflater.from(getContext()).inflate(R.layout.qs_paged_page, this, false));}}if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "+ index);mPages.get(index).addTile(tile);}if (mNumPages != index + 1) {mNumPages = index + 1;while (mPages.size() > mNumPages) {mPages.remove(mPages.size() - 1);}if (DEBUG) Log.d(TAG, "Size: " + mNumPages);mPageIndicator.setNumPages(mNumPages);setAdapter(mAdapter);mAdapter.notifyDataSetChanged();setCurrentItem(0, false);}
}
复制
至此,SystemUI 下拉状态栏快捷开关模块代码流程分析完毕。
新增一个快捷开关
0、国际惯例,先上效果图,新增一个Camera,随便用了蓝牙的图标:
1、首先在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里面添加截屏 Camera 的选项
<string name="quick_settings_tiles_default" translatable="false">wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,camera
</string>
复制
2、在 AOSP/frameworks/base/packages/SystemUI/res/values/strings.xml 里面还要加一个字符串
3、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/ 目录下创建 CameraTile.java,实现 QSTileImpl:
package com.android.systemui.qs.tiles;
import android.content.Intent;
import android.provider.MediaStore;
import android.widget.Toast;
//手动添加
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
//手动添加
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class CameraTile extends QSTileImpl<QSTile.BooleanState> {public CameraTile(QSHost host) {super(host);}@Overridepublic BooleanState newTileState() {return new BooleanState();}@Overrideprotected void handleClick() {Toast.makeText(mContext,"Camera Click",Toast.LENGTH_LONG).show();}@Overrideprotected void handleUpdateState(BooleanState state, Object arg) {state.label = mContext.getString(R.string.quick_settings_camera_label);//定义图标,随便用了蓝牙的图标state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);}@Overridepublic int getMetricsCategory() {return MetricsEvent.QS_CAMERA;}@Overridepublic Intent getLongClickIntent() {return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);}@Overrideprotected void handleSetListening(boolean listening) {}@Overridepublic CharSequence getTileLabel() {return mContext.getString(R.string.quick_settings_camera_label);}
}
复制
4、在 AOSP/frameworks/base/proto/src/metrics_constants.proto,增加常量:
5、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java,增加:
private QSTileImpl createTileInternal(String tileSpec) {// Stock tiles.switch (tileSpec) {case "wifi":return new WifiTile(mHost);// 省略部分代码case "nfc":return new NfcTile(mHost);case "camera":return new CameraTile(mHost);}// 省略部分代码
}
复制
6、整编代码,运行模拟器,有效果,棒棒哒。
Android 9.0 SystemUI 下拉状态栏快捷开关相关推荐
- Android 8.0 SystemUI下拉状态栏快捷开关
基于工作需要,基本是在Android源生代码上进行开发,从android 5.0到现在8.0,这两年碰到各种问题发现关于Android源生发开方面的特别少.于是想着开始把遇到的.解决的问题写下来,或许 ...
- Android 10.0 SystemUI下拉状态栏UI定制化开发系列(一)
1.概述 10.0定制化开发中,由于客户需求要求对整个SystemUI下拉状态栏和下拉通知栏部分的UI做定制,所以需要修改整个下拉状态栏的 UI布局页面,这要求对整个NotificationPanel ...
- Android 10.0 SystemUI下拉状态栏时间格式的修改(一)
在原生的下拉状态栏时间格式为 某月某日周几 这样的格式 客户需要修改为年月日周几 某时某分这种格式 这就需要修改 显示时间的格式 在更新时间时 按照这个格式更新就可以了 首选来看 时间控件的布局文件q ...
- Android 10.0 SystemUI下拉状态栏UI定制化开发系列(八)
目录 1.概述 2.核心代码部分 3.核心代码分析 3.1状态栏黑色透明背景的分析
- Android 10.0 SystemUI下拉状态栏UI定制化开发系列(十二)
目录 1.概述 2.核心代码 3.核心代码部分分析 3.1 NotificationStackScrollLayout.java代码分析 3.2接下来分析Activat
- Android 10 禁止SystemUI 下拉状态栏和通知栏
1.未锁屏时禁止状态栏和通知栏下拉 代码路径: frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/Keyguard ...
- Android系统定制-SystemUI-控制下拉状态栏快捷设置的下拉与关闭(通过按键控制)
一.需求场景 Android 下拉状态栏快捷设置的下拉与关闭标准设计是通过我们手指触摸去滑动顶部状态栏,但有些场景,我们可能需要通过app或者某些按键去控制,这个时候我们就需要了解下拉状态栏快捷设置的 ...
- 9.0自定义SystemUI下拉状态栏和通知栏视图(十七)之自定义通知布局构建
1.前言 在进行9.0的rom产品定制化开发中,在9.0中针对systemui下拉状态栏和通知栏的定制UI的工作开发中,原生系统的下拉状态栏和通知栏的视图UI在产品开发中会不太满足功能, 所以根据产品 ...
- 10.0 自定义SystemUI下拉状态栏和通知栏视图(八)之锁屏通知布局
1.前言 在进行10.0的系统rom产品定制化开发中,在10.0中针对systemui下拉状态栏和通知栏的定制UI的工作开发中,原生系统的下拉状态栏和通知栏的视图UI在产品开发中会不太满足功能, 所以 ...
最新文章
- .NET Core 控制台应用程序使用异步(Async)Main方法
- 会不会导致内存泄漏_可能会导致.NET内存泄露的8种行为
- 阿里 Re-rank Recommendation 读后感
- (软件工程复习核心重点)第六章实现和测试-第六节:白盒测试
- 系统地编译Hi3519过程及其处理问题思路
- 探索大神科比,30000多次投篮数据,有好玩的发现
- 【备忘】elasticsearch所有版本的x-pack-sql-jdbc下载地址
- 三相交流电路中三相负载的计算方法
- 知识图谱(Knowledge Graph)
- mysql建表语句转oracle_求大神将该MySQL建表语句改为oracle的,感激不尽。
- 电脑报独家报道:宽带升级全国真相调查
- 支付宝退款申请PHP,使用:4、退款查询
- Qt 串口调试软件自动发送数据
- 微信打开网址添加在浏览器中打开提示
- 如何定义 Symbian WINS模拟器的内存配置
- 2016版excel_开启下一个十年,全新 Microsoft 365 订阅版终于来了!
- 新员工碰到新问题 公司论坛帮解决
- android 应用置顶到最前端_Android 将后台应用切换到前台
- Web应用对接支付宝当面付解决方案
- python练习实例——水仙花数判断
热门文章
- torch Dataloader中的num_workers
- srs系列七——Vhost模式
- 架构设计:系统存储(21)——图片服务器:详细设计(1)
- WMS系统(一)成品出库
- 力扣(104.101)补9.7
- 【论文解读】R-CNN 深入浅出理解目标检测开山之作
- [原创]续一:WMI进程占用CPU过高,由Alibaba的pcUnitTest.exe文件引起
- John McCarthy:人工智能之父
- MySQL错误:Value ‘0000-00-00 00:00:00‘ can not be represented as java.sql.Timestamp
- BC26 OPEN开发之--LWM2M连接分析