本文中,我们详细了解一下 Android N(7.0)上的新增特性:Quick Settings。
Quick Settings 功能如下图所示:
该功能位于下拉的通知面板中,在用户单手指下拉通知面板的时候,Quick Settings 区域显示成一个长条,用户可以点击右上角的尖号展开这个区域。
Quick Settings 提供给用户非常便捷的按钮,用户甚至无需解锁就可以操作这个区域,通过点击 Quick Settings 中的 Tile 来切换某个功能的状态,例如打开 / 关闭手电筒,蓝牙,Wifi 等功能。这对于用户来说是非常便捷的。
使用 Quick Settings 功能非常的简单,只需要与 Tile 和 TileService 两个类打交道即可。它们的类图如下图所示:
TileService 是 android.app.Service 的子类,开发者通过继承 TileService 并覆写其对应的方法来完成功能的实现。TileService 中提供的状态回调方法如下:
方法名 | 说明 |
---|---|
onClick() | 当前 Tile 被点击了 |
onDestroy() | 当前 Tile 将要被销毁 |
onStartListening() | 当前 Tile 将要进入监听状态 |
onStopListening() | 当前 Tile 将要退出监听状态 |
onTileAdded() | 当前 Tile 被添加到 Quick Settings 中 |
onTileRemoved() | 当前 Tile 被从 Quick Settings 中删除 |
在这些状态变更的时候,开发者可以根据状态的不同来调整 Tile 的状态。调整的方法就是:先通过 TileService.getQsTile() 获取到当前 Tile,然后通过 Tile 的 setXXX 方法来修改。最后调用 Tile.updateTile() 来使刚刚的设置生效。
下面是一段代码示例。这段代码的功能是根据用户点击来将 Tile 在 Active 和非 Active 状态之间进行切换。
- private static final String SERVICE_STATUS_FLAG = "serviceStatus";
- private static final String PREFERENCES_KEY = "com.google.android_quick_settings";
- @Override public void onClick() {①Log.d("QS", "Tile tapped");
- updateTile();
- }
- // Changes the appearance of the tile.
- private void updateTile() {
- Tile tile = this.getQsTile();②boolean isActive = getServiceStatus();
- Icon newIcon;
- String newLabel;
- int newState;
- // Change the tile to match the service status.
- if (isActive) {
- newLabel = String.format(Locale.US, "%s %s", getString(R.string.tile_label), getString(R.string.service_active));
- newIcon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android_black_24dp);
- newState = Tile.STATE_ACTIVE;
- } else {
- newLabel = String.format(Locale.US, "%s %s", getString(R.string.tile_label), getString(R.string.service_inactive));
- newIcon = Icon.createWithResource(getApplicationContext(), android.R.drawable.ic_dialog_alert);
- newState = Tile.STATE_INACTIVE;
- }
- // Change the UI of the tile.
- tile.setLabel(newLabel);③tile.setIcon(newIcon);
- tile.setState(newState);
- // Need to call updateTile for the tile to pick up changes.
- tile.updateTile();④
- }
这段代码说明如下:
在实现完成这个 TileService 之后,我们还需要将其注册到 Manifest 中。TileService 需要设置一个特殊的权限和 Intent-Filter 的 Action,如下所示:
- <service android:name=".QuickSettingsService" android:icon="@drawable/ic_android_black_dp"
- android:label="@string/tile_label" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
- <intent-filter>
- <action android:name="android.service.quicksettings.action.QS_TILE" />
- </intent-filter>
- </service>
当我们将包含这个 TileService 的应用安装到设备上之后,下划通知面板然后展开 Quick Settings 区域便可以看到我们开发的 Tile 了。
我们可以通过 前面提到的 Layout Inspector 工具 来分析 Quick Settings 的结构。
Quick Settings 位于下拉的通知面板中。在布局上,这个部分通过 QSContainer 作为外部的容器,其中包含了一个 QSPanel。
QSPanel 中,包含了一个调节屏幕亮度的控件,这是通过一个 LinearLayout 来进行布局的,接下来就是 PagedTileLayout 中包含的多个 Tile 了,每个 Tile 用一个 QSTileView 来进行布局。PagedTileLayout 正如其名称所示,这是一个可以分页的 Layout。
QSContainer 中包含的元素如下图所示:
在 Android 系统中,包含两类 Tile:
Quick Settings 功能实现主要位于这个目录中: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs。
qs 目录下,包含了布局结构中用到的几个元素的实现类,包括:QSContainer,QSPanel,PagedTileLayout,QSTileView,QSIconView 等。
系统本身包含了一些预装的 Tile,例如:飞行模式的开关,位置信息的开关,热点功能的开关,手电筒功能开关等等。这些 Tile 的实现位于 qs/tiles 目录下,包含下面这些:
在 res 目录下,有一个名称为
的字符串列出了所有系统内置的 Quick Setting 的名称,它们通过逗号进行分隔。
- quick_settings_tiles_stock
- <string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
- </string>
QSTileHost 中为这里的名称和实现类做了映射:
- // QSTileHost.java
- public 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("cell")) return new CellularTile(this);
- else if (tileSpec.equals("dnd")) return new DndTile(this);
- else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
- else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
- else if (tileSpec.equals("work")) return new WorkModeTile(this);
- else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
- else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
- else if (tileSpec.equals("location")) 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("user")) return new UserTile(this);
- else if (tileSpec.equals("battery")) return new BatteryTile(this);
- else if (tileSpec.equals("saver")) return new DataSaverTile(this);
- else if (tileSpec.equals("night")) return new NightDisplayTile(this);
- // Intent tiles.
- else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this, tileSpec);
- else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this, tileSpec);
- else {
- Log.w(TAG, "Bad tile spec: " + tileSpec);
- return null;
- }
- }
TileQueryHelper 负责了 Tile 的初始化工作。在这个类中,会读取 R.string.quick_settings_tiles_stock 中的值,然后根据配置来初始化系统内置的 Quick Setting:
- // TileQueryHelper.java
- String possible = mContext.getString(R.string.quick_settings_tiles_stock);
- String[] possibleTiles = possible.split(",");
- final Handler qsHandler = new Handler(host.getLooper());
- final Handler mainHandler = new Handler(Looper.getMainLooper());
- for (int i = 0; i < possibleTiles.length; i++) {
- final String spec = possibleTiles[i];
- final QSTile < ?>tile = host.createTile(spec);
- if (tile == null || !tile.isAvailable()) {
- continue;
- }
- tile.setListening(this, true);
- tile.clearState();
- tile.refreshState();
- tile.setListening(this, false);
- qsHandler.post(new Runnable() {@Override public void run() {
- final QSTile.State state = tile.newTileState();
- tile.getState().copyTo(state);
- // Ignore the current state and get the generic label instead.
- state.label = tile.getTileLabel();
- mainHandler.post(new Runnable() {@Override public void run() {
- addTile(spec, null, state, true);
- mListener.onTilesChanged(mTiles);
- }
- });
- }
- });
- }
这段代码应该很简单,这里就不多做说明了。
对于 SystemUI 来说,除了要列出系统内置的 Quick Setting 之外,还有开发者开发的 Quick Setting 也需要读取。这部分逻辑通过 QueryTilesTask 以一个异步的 Task 来完成,这在这个异步任务中,会通过 PackageManager 查询所有开发者开发的 Quick Setting
- // TileQueryHelper.java
- private class QueryTilesTask extends AsyncTask < Collection < QSTile < ?>>,
- Void,
- Collection < TileInfo >> {@Override protected Collection < TileInfo > doInBackground(Collection < QSTile < ?>>...params) {
- List < TileInfo > tiles = new ArrayList < >();
- PackageManager pm = mContext.getPackageManager();
- List < ResolveInfo > services = pm.queryIntentServicesAsUser(new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());①String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
- for (ResolveInfo info: services) {②String packageName = info.serviceInfo.packageName;
- ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
- // Don't include apps that are a part of the default tile set.
- if (stockTiles.contains(componentName.flattenToString())) {③
- continue;
- }
- final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);④String spec = CustomTile.toSpec(componentName);
- State state = getState(params[0], spec);
- if (state != null) {
- addTile(spec, appLabel, state, false);
- continue;
- }
- if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
- continue;
- }
- Drawable icon = info.serviceInfo.loadIcon(pm);
- if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
- continue;
- }
- if (icon == null) {
- continue;
- }
- icon.mutate();
- icon.setTint(mContext.getColor(android.R.color.white));
- CharSequence label = info.serviceInfo.loadLabel(pm);
- addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
- }
- return tiles;
- }
这段代码说明如下:
来源: http://www.tuicool.com/articles/BBbMjuZ