Android(安卓)状态栏下拉列表添加自定义item开关
需求描述:客户需要在状态栏下拉列表里添加更改屏幕密度density开关按钮
Android版本:android8.1
这是一个长故事:
public class StatusBar extends SystemUI //Statusbar是继承自SystemUI的
StatusBar是个很大很大的类,里面加载了太多的对象了,在StatusBar开始的时候执行了start()方法
@Override public void start() { .... createAndAddWindows(); //创建并添加窗口 .... }
看看createAndAddWindows里
public void createAndAddWindows() { addStatusBarWindow(); } -------------------------private void addStatusBarWindow() { makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); mRemoteInputController = new RemoteInputController(mHeadsUpManager); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); }
makeStatusBarView里有上千行代码,其中有一段
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController);
SystemUIFactory.getInstance().createQSTileHost
SystemUIFactory去创建QuickStatubar
public QSTileHost createQSTileHost(Context context, StatusBar statusBar, StatusBarIconController iconController) { return new QSTileHost(context, statusBar, iconController); }
接着就进入了QSTileHost的构造方法里
public QSTileHost(Context context, StatusBar statusBar, StatusBarIconController iconController) { mIconController = iconController; mContext = context; mStatusBar = statusBar; mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER)); mQsFactories.add(new QSFactoryImpl(this)); //创建新的实体 Dependency.get(PluginManager.class).addPluginListener(this, QSFactory.class, true); Dependency.get(TunerService.class).addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = new AutoTileManager(context, this); }
创建新的QSFactoryImpl实体 new QSFactoryImpl(this)
接着在QSTileHost里的回调**onTuningChanged(String key, String newValue)**里调用了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; }
这里就去调用了QSFactoryImpl实体的createTile
public QSTile createTile(String tileSpec) { if (tileSpec.equals("wifi")) return new WifiTile(mHost); else if (tileSpec.equals("bt")) return new BluetoothTile(mHost); else if (tileSpec.equals("cell")) return new CellularTile(mHost); else if (tileSpec.equals("dnd")) return new DndTile(mHost); else if (tileSpec.equals("inversion")) return new ColorInversionTile(mHost); else if (tileSpec.equals("airplane")) return new AirplaneModeTile(mHost); else if (tileSpec.equals("work")) return new WorkModeTile(mHost); else if (tileSpec.equals("rotation")) return new RotationLockTile(mHost); else if (tileSpec.equals("flashlight")) return new FlashlightTile(mHost); else if (tileSpec.equals("density")) return new DensityTile(mHost); else if (tileSpec.equals("location")) return new LocationTile(mHost); else if (tileSpec.equals("cast")) return new CastTile(mHost); else if (tileSpec.equals("hotspot")) return new HotspotTile(mHost); else if (tileSpec.equals("user")) return new UserTile(mHost); else if (tileSpec.equals("battery")) return new BatterySaverTile(mHost); else if (tileSpec.equals("saver")) return new DataSaverTile(mHost); else if (tileSpec.equals("night")) return new NightDisplayTile(mHost); else if (tileSpec.equals("nfc")) return new NfcTile(mHost); else if (tileSpec.equals("dataconnection") && !SIMHelper.isWifiOnlyDevice()) return new MobileDataTile(mHost); else if (tileSpec.equals("simdataconnection") && !SIMHelper.isWifiOnlyDevice() && quickSettingsPlugin.customizeAddQSTile(new SimDataConnectionTile(mHost)) != null) { return (SimDataConnectionTile) quickSettingsPlugin.customizeAddQSTile( new SimDataConnectionTile(mHost)); } else if (tileSpec.equals("dulsimsettings") && !SIMHelper.isWifiOnlyDevice() && quickSettingsPlugin.customizeAddQSTile(new DualSimSettingsTile(mHost)) != null) { return (DualSimSettingsTile) quickSettingsPlugin.customizeAddQSTile( new DualSimSettingsTile(mHost)); } else if (tileSpec.equals("apnsettings") && !SIMHelper.isWifiOnlyDevice() && quickSettingsPlugin.customizeAddQSTile(new ApnSettingsTile(mHost)) != null) { return (ApnSettingsTile) quickSettingsPlugin.customizeAddQSTile( new ApnSettingsTile(mHost)); } }
这里都是根据tileSpec创建Tile,比如:flashlight就对应的创建 new FlashlightTile(mHost)
而这里的tileSpecs 来自于前面的回调onTuningChanged(String key, String newValue)
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
protected List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); String defaultTileList = res.getString(R.string.quick_settings_tiles_default); 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; }
发现 原来这个tilespace的字符来源是R.string.quick_settings_tiles_default
全局所属xml,发现是这样的
<string name="quick_settings_tiles_default" translatable="false"> wifi,bt,dnd,flashlight,density,rotation,battery,cell,airplane,cast string>
这里每一个item对应着下拉列表的一个item项
所以,我们就可以依葫芦画瓢的,在这里添加一个。
然后再createTile里,加一行我们自己的
剩下的内容就是抄袭系统是如何添加item的,但还是得了解那个QSTile对象是什么
##下面以AirplaneModeTile为例来分析怎么添加自己的QSTile
进入AirplaneModeTile,发现
public class AirplaneModeTile extends QSTileImpl<BooleanState>
它继承了QSTileImpl 以及泛型 BooleanState
实际上,createTile里,所有的tile,都是QSTileImpl的子类,分别实现了不同的功能
在AirplaneModeTile 里,我们会发现很多的重写方法,看不到调用轨迹
第一个成员变量,
private final Icon mIcon = ResourceIcon.get(R.drawable.ic_signal_airplane);
里面的xml是svg矢量图
预览效果
这不就是下拉列表里的飞行模式开关吗。
接着分析成员方法
重写方法
@Override public void handleClick() { setEnabled(!mState.value); }
处理图标点击事件
private void setEnabled(boolean enabled) { final ConnectivityManager mgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); mgr.setAirplaneMode(enabled); }
调用系统的connectivityManager去设置飞行模式开关~~
呵呵,写的这么简单
@Override public CharSequence getTileLabel() { return mContext.getString(R.string.airplane_mode); }
返回tile名字,直接从string里找
@Override protected void handleUpdateState(BooleanState state, Object arg) { final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue(); final boolean airplaneMode = value != 0; state.value = airplaneMode; state.label = mContext.getString(R.string.airplane_mode); state.icon = mIcon; if (state.slash == null) { state.slash = new SlashState(); } state.slash.isSlashed = !airplaneMode; state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.contentDescription = state.label; state.expandedAccessibilityClassName = Switch.class.getName(); }
处理状态更新,state来自于booleanState 以及arg
@Override public int getMetricsCategory() { return MetricsEvent.QS_AIRPLANEMODE; }
得到getMetricsCategory, 这个是系统必须写的,至于什么作用,猜着估计是用来标识用的,在metrics_constants.proto 这个文件里有定义,我们需要给自己的tile加一个值即可。
然后就是
@Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on); } else { return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_off); } }
很明显,就是简单的得到string
然后,, 没了,一个简单的AirplanmodeTile就写完了,抽象出来的方法只有这么几个需要重写,其他构造和处理逻辑都写在了父类里,它的父类QSTileImpl是个抽象类,不过我们不需要去重新写父类,这大概在设计者设计的时候,就已经考虑好了解耦,设计思想很明显。
然后,我们只需要重新几个自己的方法就可以了,在handleClick里就是去处理点击事件的开关,我们只需要去做自己想做的开关即可。
客户的需求是添加控制系统density的开关,
我就照着重写了个DensityTile,重写里面的方法,
在click的里面借来了开发者选项里改写系统density的代码
try { final Resources res = mContext.getResources(); final DisplayMetrics metrics = res.getDisplayMetrics(); final int newSwDp = Math.max(enable?480:320, 320); final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels); final int newDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / newSwDp; final int densityDpi = Math.max(newDensity, 120); DisplayDensityUtils.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, densityDpi); } catch (Exception e) { // TODO: display a message instead of silently failing. Slog.e(TAG, "Couldn't save density", e); }
setforceDisplayDensity是个新收获,
public static void setForcedDisplayDensity(final int displayId, final int density) { final int userId = UserHandle.myUserId(); AsyncTask.execute(() -> { try { final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); wm.setForcedDisplayDensityForUser(displayId, density, userId); } catch (RemoteException exc) { Log.w(LOG_TAG, "Unable to save forced display density setting"); } }); }
可以看到,调用的是IWindowManager接口对象,而且是直接从Global获取,而得到的对象实体,实际就是
WindowManagerService ,—public class WindowManagerService extends IWindowManager.Stub
而WindowManagerService里面还有许多其他设置系统显示的方法,所以,我们可以用这种方式,很容易的从其他地方调用windowservice里的方法,不得不感叹这是系统做的好。
最后效果:
这个开关就是设置系统density显示分辨率的开关,打开就是7201280,关闭就是480854,任务完成。
更多相关文章
- Android状态栏透明方法,与工具栏颜色一致
- Android手机应用开发(八) | 制作简单音乐播放器
- Android(安卓)Jetpack 之 LifeCycle
- Android(安卓)获取判断是否有悬浮窗权限的方法
- Android简单记录和恢复ListView滚动位置的方法
- Android(安卓)使用PDF.js浏览pdf的方法示例
- Android中Bitmap用法实例分析
- 浅谈Android(安卓)onTouchEvent 与 onInterceptTouchEvent的区别
- android 倒计时的控件,以动画的形式平滑的完成数字的过度