Android5.1 快捷開關如何新增和重新整理狀態
Android 5.1的快捷開關的新增和重新整理機制和Android6、Android7差不多是一樣的。雖然現在Android7.0的快捷開關還沒有認真的去研讀,只是稍微的看了一下。Android6.0和Android7.0相比較於Android5.0的快捷開關增加了不少的程式碼。增加的程式碼主要是增加了下拉快捷面板的時候其中的一些動畫,還有就是Android7.0中增加了快捷開關的自定義排序功能。但是萬變不離其宗。下面切入到正題。
先看一下定製的快捷開關的UI樣式。
快捷面板上面有很多的快捷開關,但是其中各個快捷開關的是如何運作的還不是很清楚。
好了言歸正傳。
新增快捷開關首先需要去配置檔案config.xml檔案中去新增快捷開關的名稱。
config.xml檔案的路徑是:frameworks/base/packages/SystemUI/res/values/config.xml中。其關鍵程式碼如下:
<!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> wifi,bt,data,airplane,audioprofile,hotspot,dnd,powersave,rotation,location,flashlight,takescreenshot </string>
關鍵的java程式碼主要是在:frameworks/base/packages/SystemUI/com/android/systemui/qs 下面,包括QSPanel.java、
QSTile.java、QSTileView.java等。
在手機啟動的時候,首先會去載入和建立和初始化PhoneStatusBar.java (frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/)中的控制元件和類。當然也包括快捷面板的
初始化操作。PhoneStatusBar中關於快捷面板的關鍵程式碼如下:
// Set up the quick settings tile panel mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);//找到QSPanel if (mQSPanel != null) {
//初始化 QSTileHost
final QSTileHost qsh = new QSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, mHotspotController,
mCastController, mFlashlightController,
mUserSwitcherController, mKeyguardMonitor,
mSecurityController,
/// M: add HotKnot in quicksetting
mHotKnotController,
/// M: add AudioProfile in quicksetting
mAudioProfileController
);
mQSPanel.setHost(qsh);
//通過QSTileHost獲得的各個快捷開關(qsh.getTiles())加入到QSPanel中。
mQSPanel.setTiles(qsh.getTiles());
//初始化調節螢幕亮度控制器
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
qsh.setCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
}
});
}
通過跟蹤上面的程式碼,我們可以看到 需要通過QSTileHost去獲取快捷開關(qsh.getTiles())。那麼,qsh.getTiles()
是如何去獲取到各個快捷開關的呢?我們一起去看看吧。開啟QSTileHost.java類。找到getTiles()方法。
@Override
public Collection<QSTile<?>> getTiles() {
return mTiles.values();
}
mTiles的型別:
LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
找到mTiles是在哪賦值的,通過查詢可以找到是在下面的這個類中。
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
final List<String> tileSpecs = loadTileSpecs(newValue);
if (tileSpecs.equals(mTileSpecs)) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
tile.getValue().destroy();
}
}
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
if (mTiles.containsKey(tileSpec)) {
newTiles.put(tileSpec, mTiles.get(tileSpec));
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
newTiles.put(tileSpec, createTile(tileSpec));
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
}
}
}
mTileSpecs.clear();
mTileSpecs.addAll(tileSpecs);
mTiles.clear();
mTiles.putAll(newTiles);
if (mCallback != null) {
mCallback.onTilesChanged();
}
}
上面最主要的兩個方法是loadTileSpecs() 方法和 createTile()方法。那我們看看loadTileSpecs方法的具體作用是什麼。
//loadTileSpecs() 此方法主要是去之前配置好的config.xml檔案中獲取各個快捷開關的名稱。然後拆分字串。
protected List<String> loadTileSpecs(String tileList) {
final Resources res = mContext.getResources();
//獲取Config.xml中配置需要顯示的快捷開關的名稱。
String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
// M: Add extra tiles @{
defaultTileList += "," + res.getString(R.string.quick_settings_tiles_extra);//default
// @}
/// M: Customize the quick settings tile order for operator. @{
IQuickSettingsPlugin quickSettingsPlugin = PluginFactory.getQuickSettingsPlugin(mContext);
defaultTileList = quickSettingsPlugin.customizeQuickSettingsTileOrder(defaultTileList);
/// M: Customize the quick settings tile order for operator. @}
Log.d(TAG, "loadTileSpecs() default tile list: " + defaultTileList);
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;
}
//createTile() 方法的作用是 建立各個快捷開關的物件。
private QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi") && !WCN_DISABLED) return new WifiTile(this);
else if (tileSpec.equals("bt") && !WCN_DISABLED) return new BluetoothTile(this);
else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
else if (tileSpec.equals("cell")) return new CellularTile(this);
else if (tileSpec.equals("data")) return new DataConnectionTile(this);
else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
// else if (tileSpec.equals("lte") && TelephonyManagerSprd.isDeviceSupportLte()) return new LteServiceTile(this);
// else if (tileSpec.equals("lte") && isDeviceSupportLte()) return new LteServiceTile(this);
else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
else if (tileSpec.equals("location") && !WCN_DISABLED) return new LocationTile(this);
else if (tileSpec.equals("cast")) return new CastTile(this);
else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
else if (tileSpec.equals("diabolo")) return new DiaboloTile(this);
else if (tileSpec.equals("dnd")) return new DndTile(this);
else if (tileSpec.equals("audioprofile") ) //&& SIMHelper.isMtkAudioProfilesSupport()
return new AudioProfileTile(this);
// else if (tileSpec.equals("interruption")) return new InterruptionsTile(this);
else if (tileSpec.equals("powersave")) return new PowerSaveTile(this);
else if (tileSpec.equals("takescreenshot")) return new TakeScreenShotTile(this);
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
好了,經過上面的分析,終於知道配置檔案的作用。知道使如何建立的了之後,但是還沒有完。下面來看看他是如何
重新整理和更新快捷開關的圖示和文字的。
我們還是需要從上面的 PhoneStateBar.java 中的 程式碼中繼續往下分析,要分析的程式碼如下:
//通過QSTileHost獲得的各個快捷開關(qsh.getTiles())加入到QSPanel中。
mQSPanel.setTiles(qsh.getTiles());
關鍵的是QSPanel.java類中的setTiles()方法。
public void setTiles(Collection<QSTile<?>> tiles) {
for (TileRecord record : mRecords) {
removeView(record.tileView);
}
mRecords.clear();
int row = tiles.size() / mColumns + 1;
if (mDeviderViewGroup == null) {
mDeviderViewGroup = new View[row];
} else {
for (int i = 0; i < mDeviderViewGroup.length; i++) {
removeView(mDeviderViewGroup[i]);
}
mDeviderViewGroup = new View[row];
}
for (int i = 0; i < row; i++) {
addDividerLine(i);
}
for (QSTile<?> tile : tiles) {
addTile(tile);
}
if (isShowingDetail()) {
mDetail.bringToFront();
}
}
上面setTiles()方法中,需要注意的是addTile()方法。如果開啟QSPanel.java 類,我們會知道QSPanel類
繼承於ViewGroup的。下面我們在來看看addTile()方法的做了寫什麼。
private void addTile(final QSTile<?> tile) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = tile.createTileView(mContext);
r.tileView.setVisibility(View.GONE);
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
if (state.visible && !mGridContentVisible) {
// We don't want to show it if the content is hidden,
// then we just set it to invisible, to ensure that it gets
// visible again
visibility = INVISIBLE;
}
setTileVisibility(r.tileView, visibility);
r.tileView.onStateChanged(state);
}
@Override
public void onShowDetail(boolean show) {
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.setCallback(callback);
final View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
r.tile.click();
}
};
final View.OnClickListener clickSecondary = new View.OnClickListener() {
@Override
public void onClick(View v) {
r.tile.secondaryClick();
}
};
final View.OnLongClickListener longClick = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
r.tile.longClick();
return true;
}
};
r.tileView.init(click, clickSecondary, longClick);
r.tile.setListening(mListening);
callback.onStateChanged(r.tile.getState());
r.tile.refreshState();
mRecords.add(r);
addView(r.tileView);
}
我們可以看到addTile()方法的最後面是呼叫的addView方法。說明是將各個快捷開關新增到QSPanel的ViewGroup當中。
成為QSPanel的子控制元件。
下面我們在來看看 子控制元件。這裡以手電筒(FlashlightTile.java)為例來說明其實如何來重新整理圖示和文字的。
//FlashlightTile.java
public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
FlashlightController.FlashlightListener {
......
@Override
protected void handleClick() {
if (ActivityManager.isUserAMonkey()) {
return;
}
MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
boolean newState = !mState.value;
refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
mFlashlightController.setFlashlight(newState);
Settings.System.putInt(mContext.getContentResolver(), "open_flashlight_by_quicksetting_panel", newState ? 1 : 0); // 1 開啟 0 關閉
}
.......
}
FlashlightTile.java 中用來更新手電筒圖示和文字的程式碼是refreshState() 去重新整理。當呼叫refreshState的時候
會去呼叫QSTile.java類中的freshState(Object arg)方法。
protected final void refreshState(Object arg) {
mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
}
protected final class H extends Handler {
private static final int SET_CALLBACK = 1;
private static final int CLICK = 2;
private static final int SECONDARY_CLICK = 3;
private static final int LONG_CLICK = 4;
private static final int REFRESH_STATE = 5;
private static final int SHOW_DETAIL = 6;
private static final int USER_SWITCH = 7;
private static final int TOGGLE_STATE_CHANGED = 8;
private static final int SCAN_STATE_CHANGED = 9;
private static final int DESTROY = 10;
private H(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
String name = null;
try {
if (msg.what == SET_CALLBACK) {
name = "handleSetCallback";
handleSetCallback((QSTile.Callback)msg.obj);
} else if (msg.what == CLICK) {
name = "handleClick";
mAnnounceNextStateChange = true;
handleClick();
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
handleSecondaryClick();
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
handleLongClick();
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
} else if (msg.what == SHOW_DETAIL) {
name = "handleShowDetail";
handleShowDetail(msg.arg1 != 0);
} else if (msg.what == USER_SWITCH) {
name = "handleUserSwitch";
handleUserSwitch(msg.arg1);
} else if (msg.what == TOGGLE_STATE_CHANGED) {
name = "handleToggleStateChanged";
handleToggleStateChanged(msg.arg1 != 0);
} else if (msg.what == SCAN_STATE_CHANGED) {
name = "handleScanStateChanged";
handleScanStateChanged(msg.arg1 != 0);
} else if (msg.what == DESTROY) {
name = "handleDestroy";
handleDestroy();
} else {
throw new IllegalArgumentException("Unknown msg: " + msg.what);
}
} catch (Throwable t) {
final String error = "Error in " + name;
Log.w(TAG, error, t);
mHost.warn(error, t);
}
}
}
protected void handleRefreshState(Object arg) {
handleUpdateState(mTmpState, arg);
final boolean changed = mTmpState.copyTo(mState);
if (changed) {
handleStateChanged();
}
}
跟蹤程式碼,最終發現呼叫FlashlightTile.java中的refreState()方法,最後會呼叫QSTile.java的handleRefreshState
方法。handleRefreshState方法又會呼叫handleUpdateState和handleStateChanged() 兩個方法。handleUpdateState
方法會去呼叫QSPanel的子控制元件FlashlightTile.java中的handleUpdateState方法。去重新獲得圖示和文字。最後呼叫
handleStateChanged去回撥QSpanel.java 中的程式碼。
private void handleStateChanged() {
boolean delayAnnouncement = shouldAnnouncementBeDelayed();
if (mCallback != null) {
mCallback.onStateChanged(mState);
if (mAnnounceNextStateChange && !delayAnnouncement) {
String announcement = composeChangeAnnouncement();
if (announcement != null) {
mCallback.onAnnouncementRequested(announcement);
}
}
}
mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
}
上邊標記的橘黃色文字的地方所示,回去呼叫QSPanel.java中橘黃色程式碼的地方(當上文中描述QSPanel程式碼的地方
檢視)。最終會呼叫r.tileView.onStateChanged(state)
r.tileView.onStateChanged(state);
這段程式碼會去呼叫QSTileView.java 中的onStateChanged方法。自己去檢視onStateChanged原始碼的話,發現
其實際上是會根據傳進來的引數state去更新圖示和文字的。