作者:jason.chen
从状态栏往下拉的时候,就会出现“快速设置”界面,如下图所示。快速启动界面可以让用户快速设置和操作。对于急需或频繁使用的控件和操作,保留“快速设置”图块,且不应将其用作启动应用的快捷方式。android7.0添加了新的API,让开发者可以为自己的应用添加快速设置图标。这篇文件将要分析快速启动的设计。当然,只是个人见解。
快速设置图标有两种类型,一种是system UI预置的(QSTile),另一种是第三方应用添加的(CustiomTile)。system UI使用Tile来代表一个快速设置。本文只介绍system UI预置的QSTile,关于第三方应用的Tile后续再分析。
QSTile:快速设置Tile的基类,快速设置都继承自这个类。它使用Host提供的Looper, 管理着快速设置(以下文章都用Tile来指代快速设置)的状态(State)变化。Tile通过重载handleUpdateState方法来更新状态。如果监听到状态变化,或者点击事件需要更新状态,使用refreshState来更新State。
QSTileHost: Host的实现,管理Tile 状态的变化。它包含各种Tile的Control类,用来设置和监听Tile。
State: Tile的状态,包含图标,名称等信息。
H:继承自Handler, 通过looper跑在QSTileHost的线程里。用来处理Tile的各种事件。
Tunable: 接口,配置变化的回调。QSTileHost实现这个接口。
TunerSercice:用来监听各种配置变化。
这里从创建View开始跟踪(之前的流程可以参考http://blog.csdn.net/zhudaozhuan/article/details/50817180),过程如下图所示:
在TunerService的addTunable的方法,会监听ContentProvider中的Tile的变化(一般是增加或减少Tile),监听的函数是registerContentObserver。Tile是存储在Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser),相关代码如下:
private void addTunable(Tunable tunable, String key) {
if (!mTunableLookup.containsKey(key)) {
mTunableLookup.put(key, new ArraySet());
}
mTunableLookup.get(key).add(tunable);
Uri uri = Settings.Secure.getUriFor(key);
if (!mListeningUris.containsKey(uri)) {
mListeningUris.put(uri, key);
//listening for the tile change
mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
}
// Send the first state.
String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
tunable.onTuningChanged(key, value);
}
当Tile发生变化或第一次添加的时候,会执行QSTileHost的onTuningChanged的方法,在这个方法里会创建所有需要显示的Tile。部分代码如下:
for (String tileSpec : tileSpecs) {
QSTile> tile = mTiles.get(tileSpec);
if (tile != null && (!(tile instanceof CustomTile)
|| ((CustomTile) tile).getUser() == currentUser)) {
if (DEBUG) Log.d(TAG, "Adding " + tile);
tile.removeCallbacks();
newTiles.put(tileSpec, tile);
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
tile = createTile(tileSpec);
if (tile != null && tile.isAvailable()) {
tile.setTileSpec(tileSpec);
newTiles.put(tileSpec, tile);
}
} 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();
}
createTile方法,根据tileSpec创建响应的Tile,使用的是工厂方法。最终会把创建的Tile都存储在QSTileHost的mTiles,如果有注册QSTile.Host.Callback的监听,也会回调响应的onTilesChanged方法。
自此,QSTileHost已经获取到了Tile的数据。
显示Tile的地方有两个,请看前面的两张图,一个是未展开的状态,只显示前面几个Tile;另一个是全部展开的状态,可以显示所有的Tile,还有其他的设置功能。这里先介绍未展开的Tile界面。
QSPanel: 用来显示快速设置的界面。
QuickQSPanel: QSPanel的子类,里面包含一个HeaderTileLayout,用来显示少数几个Tile的界面。在快速设置界面的顶部。
HeaderTileLayout: QuickQSPanel创建的View,用来显示少数几个Tile的界面。
TileRecord: 包含Tile数据,Tile界面(QSTileBaseView)等数据。
QSTileBaseView: 继承自LinearLayout,用来显示Tile。不显示标题。
QSTileView: 继承自QSTileBaseView, 用来显示Tile,会显示标题。HeaderTileLayout不使用这个。
未展开的Tile显示在QuickQSPanel的HeaderTileLayout里。可以查看QuickStatusBarHeader的xml文件:quick_status_bar_expanded_header.xml。在QuickQSPanel的构造函数中,创建并加入HeaderTileLayout:
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
if (mTileLayout != null) {
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
}
removeView((View) mTileLayout);
}
mTileLayout = new HeaderTileLayout(context);
mTileLayout.setListening(mListening);
addView((View) mTileLayout, 1 /* Between brightness and footer */);
}
它的父类QSPanel的构造函数会加入一个Brightness View和Footer View, 这里并不需要显示。加载Tile的时序图如下所示:
在QSPanel的addTile中,会去创建TileRecord。TileRecord包含Tile的界面和tile的数据,同时还包含一个callback,这在处理事件的时候,会用到。然后在HeaderTileLayout的add Tile方法,会把这个QSTileBaseView添加进去。相关代码代码如下:
protected void addTile(final QSTile> tile, boolean collapsedView) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = createTileView(tile, collapsedView);
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
drawTile(r, state);
}
@Override
public void onShowDetail(boolean show) {
// Both the collapsed and full QS panels get this callback, this check determines
// which one should handle showing the detail.
if (shouldShowDetail()) {
QSPanel.this.showDetail(show, r);
}
}
@Override
public void onToggleStateChanged(boolean state) {
if (mDetailRecord == r) {
fireToggleStateChanged(state);
}
}
@Override
public void onScanStateChanged(boolean state) {
r.scanState = state;
if (mDetailRecord == r) {
fireScanStateChanged(r.scanState);
}
}
@Override
public void onAnnouncementRequested(CharSequence announcement) {
announceForAccessibility(announcement);
}
};
r.tile.addCallback(callback);
r.callback = callback;
final View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
onTileClick(r.tile);
}
};
final View.OnLongClickListener longClick = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
r.tile.longClick();
return true;
}
};
r.tileView.init(click, longClick);
r.tile.refreshState();
mRecords.add(r);
if (mTileLayout != null) {
mTileLayout.addTile(r);
}
}
主要有点击事件,长按事件等。
onClick的事件,参考前面addTile的代码,里面有创建一个Click类,用来监听onClick事件。时序图如下:
1)QuickQSPanel有重载了QSPanle的onTileClick事件,会执行handleSecondaryClick。从上图可以看出,它默认的行为也是执行QSTile的handleClick事件。每个QSTile的子类都可以重载handleSecondaryClick方法,来区别未展开和展开的Tile点击事件。所以,点击QuickQSPanel的Tile界面,执行的是handleSecondaryClick。
2)QSPanel的点击事件流程。触发点击事件后,执行QSTile的handleClick方法。这是一个虚方法,各个QSTile的子类需要重载这个方法。有些Tile会在这里执行刷新状态refreshState, 有些Tile会执行showDetail。
3)refreshState的流程,QSTile的子类,要在这里更新Tile的State
4)showDetail的流程。这里有一个要注意的地方是,在QSTile.Callback的onShowDetail方法中,会判断shouldShowDetail的方法。这个方法在未展开的panel里,返回的是!mExpanded, 而在展开的Panel里,返回的是
QuickQSPanle是QSPanel的子类,可能会重载QSPanel的方法。跟踪代码的时候,需要注意是否有重载。像onTileClick就有重载。
长按事件也是在QSPanl的addTile方法里创建的,最后会执行到handleLongClick的方法。在这里会从getLongClickIntent里面,获取一个Intent, 启动该Intent的activity。getLongClickIntent是一个虚方法,每个QSTile的子类, 需要重载该方法。