快速设定面板上快捷开关的加载流程,包括图标等的加载和点击事件等的处理过程,以及创建一个快捷开关的主要过程(以增加一个锁屏开关为例)。本文所讨论的Android版本为5.1。
-
快捷开关的加载流程
资源模块的加载
-
config.xml参数配置
config.xml是程序的一些配置信息。在创建快捷开关时需要按照参数配置文件config.xml来加载快捷开关信息。参考源码QSTileHost.java的实现,主要过程:
(1) QSTileHost类在构造函数里调用recreateTiles() 方法;
(2) 在recreateTiles() 方法里loadTileSpecs();
(3) loadTileSpecs() 方法里通过mContext.getResources().getString(R.string.quick_settings _tiles_default) 从config.xml中加载快捷开关规 格”quick_settings_tiles_default” 这个配置项便确定了默认情况下快速设定面板上显示哪些开关以及开关的显示顺序;
(4) 接下来创建快捷开关createTile() ,通过从config.xml文件中获取的配置信息实例化每个快捷开关;
(5) 最后将创建的快捷开关保存在成员变量mTiles列表里,通过调用QSTileHost.getTiles() 可以获取到所有的快捷开关。
-
字符和图标的加载
字符资源保存在各个value文件夹下的String.xml文件内。图标使用的是矢量图片,保存在drawable文件夹下。字符和图标的加载在每个快捷开关类文件的handleUpdateState() 方 法里进行。
每个快捷开关类都需要继承QSTile<TState extends State>类(详细的说明在2.3节讨论)。这里的TState是快捷开关的状态,会作为参数传到handleUpdateState() 方法。在 handleUpdateState() 方法里将字符和图标资源赋值给state.label和state.icon即可。
-
动画的加载
动画资源与图标一起保存在drawable文件夹下,一般都是打开和关闭成对存在的。动画的加载是通过在每个快捷开关类里实例化AnimationIcon类,资源id将作为参数,然后会在点 击事件处理中调用。
PhoneStatusBar创建view
前面提到在QSTileHost.createTile() 方法里创建了快捷开关,并且可以通过调用getTiles() 方法得到所有的快捷开关,接下来看快捷开关加载到快速设定面板的实现。参考 PhoneStatusBar. makeStatusBarView() 方法的实现,主要完成的部分:
(1) 首先初始化快捷开关面板mQSPanel,mQSPanel判空后实例化QSTileHost保存在对象qsh上;
(2) 通过调用mQSPanel.setTiles(qsh.getTiles()) 将每个快捷开关添加到mQSPanel上。另外,在配置有变化时,在PhoneStatusBar.updateResources() 方法里通过调用 mQSPanel. updateResources() 更新快捷开关面板;在解锁屏时,在PhoneStatusBar. hideKeyguard () 方法里通过调用mQSPanel. refreshAllTiles () 刷新快捷开关面板。
用户交互的处理
对用户动作的监听响应是在每个快捷开关类里处理。
前面提到,每个快捷开关类都需要继承QSTile<TState extends State>类。
QSTile<TState extends State> implements Listenable是快捷开关的基类,通过继承它来创建一个快捷开关。需要实现两个抽象方法handleClick() 和handleUpdateState() 。所以可以看出QSTile主要负责点击事件的处理和快捷开关状态的管理。消息的传递通过Handler机制完成。
(1) 动作的监听响应:
1) 在handleClick() 方法里处理点击事件;打开/关闭的动画也在这里调用;
2) 部分开关需要重写handleSecondaryClick() 方法,例如Wi-Fi和蓝牙开关,在handleSecondaryClick() 方法里打开详情页面;
3) 还有开关需要重写handleLongClick() ,例如反色和热点开关,在这里打开询问是否要隐藏的对话框;
4) 在setListening() 方法里添加回调监听,接受action和注册广播等。
(2) 状态的管理:
1) 状态管理通过一个由Host提供的looper来进行。每个快捷开关在handleUpdateState()中更新状态。回调影响状态要通过快捷开关的工作looper调用refreshState() 来触 发另一个状态更新。
2) 状态类有三种,State类以及继承自State类的BooleanState类和SignalState类。需要判断开关与否的状态的快捷开关继承QSTile<QSTile.BooleanState>,包括飞行 模式、反色、手电筒、热点、定位、自动旋转、蓝牙和屏幕投射开关等;还需要判断连接等状态的快捷开关继承QSTile<QSTile.SignalState>,例如Wi-Fi和移动数据网 络开关;其他直接继承QSTile<QSTile.State>。
(3) 另外,Wi-Fi和蓝牙开关需要重写supportsDualTargets() 方法和getDetailAdapter() 方法。因为这两个开关是绘制在一排两个开关的布局上而且需要显示详情页面。
-
创建一个快捷开关
以增加一个锁屏的快捷开关为例。
资源模块的增加
通过2.1节的讨论可以知道,需要修改config.xml文件、String.xml文件和添加矢量图xml文件或添加一张png图片。锁屏开关点击时不需要动画,因此不添加动画xml文件
(1) 在\frameworks\base\packages\SystemUI\res\values\config.xml里找到 "quick_settings_ tiles_default",添加lockscreen,用“,”隔开:
<string name="quick_settings_tiles_default" translatable="false">
wifi,bt,inversion,cell,airplane,rotation,flashlight,location,cast,hotspot,lockscreen
</string>
(2) 在\frameworks\base\packages\SystemUI\res\values\string.xml里添加:
<string name="quick_settings_lockscreen_label">
"lockscreen"
</string>
(3) 在\frameworks\base\packages\SystemUI\res\values values-zh-rCN\string.xml里添加:
<string name="quick_settings_lockscreen_label">"锁屏"</string>
其他语言在相应的values文件夹下对应的string.xml文件里添加。
(4) 在\frameworks\base\packages\SystemUI\res\drawable-hdpi文件夹里添加图片ic_qs_locks- creen.png,也可以在drawable文件夹下添加矢量图xml文件;
创建LockScreenTile类
在/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles目录下创建LockScreenTile.java。
(1) 新建锁屏快捷开关类LockScreenTile extends QSTile<QSTile.BooleanState>,继承QSTile类;
(2) newTileState() 方法直接返回一个BooleanState的实例;
@Override
protected BooleanState newTileState() {
return new BooleanState();
}
(3) handleClick() 方法里响应点击事件进行锁屏,调用PowerManager.goToSleep() 方法;
@Override
protected void handleClick() {
setEnabled(!mState.value);
}
private void setEnabled(boolean enable) {
if (enable) {
final PowerManager pmr = (PowerManager) mContext
.getSystemService(Context.POWER_SERVICE);
pmr.goToSleep(SystemClock.uptimeMillis());
}
}
(4) handleUpdateState() 方法里设置锁屏开关的状态,包括图标和字符;
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_lockscreen);
state.visible = true;
state.label = mContext.getString(R.string.quick_settings_lockscreen_label);
}
(5) 实例化LockScreenTile,在QSTileHost. createTile() 方法里添加:
private QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi"))
return new WifiTile(this);
else if (tileSpec.equals("bt"))
return new BluetoothTile(this);
……
else if (tileSpec.equals("lockscreen"))
return new LockScreenTile(this);
else if (tileSpec.startsWith(IntentTile.PREFIX))
return IntentTile.create(this, tileSpec);
else
throw new IllegalArgumentException("Bad tile spec: "+ tileSpec);
}