上次我们研究了常态显示下的状态栏,这篇我们来研究下拉后状态栏,页面是status_bar_expanded.xml
我们将下拉后的状态栏拆分来看,首先看QS快捷控制面板
关于QS快捷键我们可以分为两个类型stock和tileservice,stock是在源码中进行添加,tileService则是android7.0时谷歌添加的一个专门可以将第三方应用显示在QS快捷面板中的api,类似红包助手之类的,跟第三应用添加到设置中逻辑大致类似,通过在activity在清单文件中配置对应的action来搜索获取数据,然后控制点击事件和其它一些内容则是继承了TileService的类
stock类型的数据获取是在QSTileHost中获取,QSTileHost在创建的时候会执行TunerServiceImpl类中的addTunable方法,我们看一下这个方法的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99//TunerServiceImpl private void addTunable(Tunable tunable, String key) { ... //注册内容观察者 Uri uri = Settings.Secure.getUriFor(key); Log.d("ssss","uri = "+uri); if (!mListeningUris.containsKey(uri)) { mListeningUris.put(uri, key); mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); } //通过SettingsProvider来获取QS列表 String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); //调用onTuningChanged方法获取QS快捷面板数据 tunable.onTuningChanged(key, value); } //QSTileHost @Override public void onTuningChanged(String key, String newValue) { ... final List<String> tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); tile.getValue().destroy(); }); final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>(); for (String tileSpec : tileSpecs) { QSTile tile = mTiles.get(tileSpec); if (tile != null && (!(tile instanceof CustomTile) || ((CustomTile) tile).getUser() == currentUser)) { if (tile.isAvailable()) { if (DEBUG) Log.d(TAG, "Adding " + tile); tile.removeCallbacks(); if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { tile.userSwitch(currentUser); } newTiles.put(tileSpec, tile); } else { tile.destroy(); } } else { if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec); try { tile = createTile(tileSpec); if (tile != null) { if (tile.isAvailable()) { tile.setTileSpec(tileSpec); newTiles.put(tileSpec, tile); } else { tile.destroy(); } } } catch (Throwable t) { Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); } } } ... } protected List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); //获取QS快捷面板里的需要显示的内容 String defaultTileList = res.getString(R.string.freeme_quick_settings_tiles); ... 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; } public QSTile createTile(String tileSpec) { for (int i = 0; i < mQsFactories.size(); i++) { //创建tile QSTile t = mQsFactories.get(i).createTile(tileSpec); if (t != null) { return t; } } // M: @ { if (mQuickSettingsExt != null && mQuickSettingsExt.doOperatorSupportTile(tileSpec)) { // WifiCalling return (QSTile) mQuickSettingsExt.createTile(this, tileSpec); } // @ } return null; }
创建Tile是再QSFactoryImpl类里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.handleStale(); // Tile was just created, must be stale. } return tile; } private QSTileImpl createTileInternal(String tileSpec) { /// M: Add extra tiles in quicksetting @{ Context context = mHost.getContext(); IQuickSettingsPlugin quickSettingsPlugin = OpSystemUICustomizationFactoryBase .getOpFactory(context).makeQuickSettings(context); /// @} // Stock tiles. switch (tileSpec) { case "wifi": return new WifiTile(mHost); case "bt": return new BluetoothTile(mHost); case "cell": return new CellularTile(mHost); case "dnd": return new DndTile(mHost); case "inversion": return new ColorInversionTile(mHost); case "airplane": return new AirplaneModeTile(mHost); case "work": return new WorkModeTile(mHost); case "rotation": return new RotationLockTile(mHost); case "flashlight": return new FlashlightTile(mHost); case "location": return new LocationTile(mHost); case "cast": return new CastTile(mHost); case "hotspot": return new HotspotTile(mHost); case "user": return new UserTile(mHost); case "battery": return new BatterySaverTile(mHost); case "saver": return new DataSaverTile(mHost); case "night": return new NightDisplayTile(mHost); case "nfc": return new NfcTile(mHost); } ... return null; }
加载流程
关于显示的图标及标签,以蓝牙BluetoothTile为例,标签和图标是在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65@Override protected void handleUpdateState(BooleanState state, Object arg) { final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; final boolean enabled = transientEnabling || mController.isBluetoothEnabled(); final boolean connected = mController.isBluetoothConnected(); final boolean connecting = mController.isBluetoothConnecting(); state.isTransient = transientEnabling || connecting || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON; state.dualTarget = true; state.value = enabled; if (state.slash == null) { state.slash = new SlashState(); } /*/ freeme.gouzhouping, 20180117. FreemeAppTheme, qs container. state.slash.isSlashed = !enabled; //*/ state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.secondaryLabel = TextUtils.emptyIfNull( getSecondaryLabel(enabled, connecting, connected, state.isTransient)); if (enabled) { if (connected) { /*/ freeme.gouzhouping, 20180117. FreemeAppTheme, qs container. state.icon = new BluetoothConnectedTileIcon(); /*/ state.icon = ResourceIcon.get(R.drawable.freeme_ic_qs_bluetooth); //*/ if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) { state.label = mController.getConnectedDeviceName(); } state.contentDescription = mContext.getString(R.string.accessibility_bluetooth_name, state.label) + ", " + state.secondaryLabel; } else if (state.isTransient) { /*/ freeme.gouzhouping, 20180117. FreemeAppTheme, qs container. state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation); /*/ state.icon = ResourceIcon.get(R.drawable.freeme_ic_qs_bluetooth); //*/ state.contentDescription = state.secondaryLabel; } else { /*/ freeme.gouzhouping, 20180117. FreemeAppTheme, qs container. state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); /*/ state.icon = ResourceIcon.get(R.drawable.freeme_ic_qs_bluetooth); //*/ state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth) + "," + mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { /*/ freeme.gouzhouping, 20180117. FreemeAppTheme, qs container. state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); /*/ state.icon = ResourceIcon.get(R.drawable.freeme_ic_qs_bluetooth); //*/ state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth); state.state = Tile.STATE_INACTIVE; } state.dualLabelContentDescription = mContext.getResources().getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); state.expandedAccessibilityClassName = Switch.class.getName(); }
进行设置state.label值为标签,state.icon为图标
关于图标颜色及大小控制,根据方法追踪,最后发现是在QSIconViewImpl中控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public QSIconViewImpl(Context context) { super(context); final Resources res = context.getResources(); //*/ freeme.songyan, 20190103. simple launcher. mIconSizePx = res.getDimensionPixelSize(SimpleLauncherObserver.getInstance(mContext).isShowMode() ? R.dimen.qs_tile_icon_size_simple_launcher : R.dimen.qs_tile_icon_size); /*/ mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size); //*/ mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon); mIcon = createIcon(); addView(mIcon); } private static final int TINT_COLOR_ON = R.color.freeme_tile_icon_on; private static final int TINT_COLOR_OFF = R.color.freeme_tile_icon_off; private static final int TINT_COLOR_UNAVAILABLE = R.color.freeme_tile_icon_unavailable; private void tintColor(Context context, State state, Drawable d) { if (state.state == Tile.STATE_UNAVAILABLE) { d.setTint(context.getResources().getColor(TINT_COLOR_UNAVAILABLE)); } else { d.setTint(context.getResources().getColor( (state != null && state instanceof QSTile.BooleanState && ((QSTile.BooleanState) state).value) || (state != null && state.state == Tile.STATE_ACTIVE) ? TINT_COLOR_ON : TINT_COLOR_OFF )); } }
同样,关于字体颜色及大小的控制是在QSTileView中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public QSTileView(Context context, QSIconView icon, boolean collapsedView) { super(context, icon, collapsedView); ... FontSizeUtils.updateFontSize(mLabel, SimpleLauncherObserver.getInstance(mContext).isShowMode() ? R.dimen.qs_tile_text_size_simple_launcher : R.dimen.qs_tile_text_size); } private final int TINT_COLOR_ON = getContext().getColor(R.color.freeme_tile_label_on); private final int TINT_COLOR_OFF = getContext().getColor(R.color.freeme_tile_label_off); private void tintColor(QSTile.State state, TextView tv) { tv.setTextColor( (state != null && state instanceof QSTile.BooleanState && ((QSTile.BooleanState) state).value) || (state != null && state.state == Tile.STATE_ACTIVE) ? TINT_COLOR_ON : TINT_COLOR_OFF ); if (state.dualTarget) { ((ImageView) mExpandIndicator).setColorFilter((state != null && state instanceof QSTile.BooleanState && ((QSTile.BooleanState) state).value) || (state != null && state.state == Tile.STATE_ACTIVE) ? TINT_COLOR_ON : TINT_COLOR_OFF); ((ImageView) mExpandIndicator).invalidate(); } }
我们看一下stock类型是如何跟任务栏中的图标互通的,例如蓝牙,我们看一下BluetoothTile中的内容
1
2
3
4
5
6
7
8
9
10
11
12
13//点击事件 @Override protected void handleClick() { // Secondary clicks are header clicks, just toggle. //获取当前状态 final boolean isEnabled = mState.value; // Immediately enter transient enabling state when turning bluetooth on. //更改状态 refreshState(isEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING); //与任务栏中的图标和设置内的内容互通 mController.setBluetoothEnabled(!isEnabled); }
refreshState方法最终会调用上面我们说过的handleUpdateState方法,根据点击后的状态来修改对应的样式,我们再看一下mController.setBluetoothEnabled,这个方法BluetoothControllerImpl中
1
2
3
4
5
6
7@Override public void setBluetoothEnabled(boolean enabled) { if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled); } }
蓝牙的流程是通过控制器获取adapter然后再将值传入IBander进行跨进程通信,QS快捷面板跟设置进行互通最主要的就是IBander,其它的只是辅助的内容
与任务栏中的图标互通,首先PhoneStatusBarPolicy实现了蓝牙的接口,并且进行添加,然后BluetoothEventManager里通过广播监听蓝牙的改变,然后将改变后的数据通过接口传递给BluetoothControllerImpl,BluetoothControllerImpl在创建的时候就实现了接口并添加,BluetoothControllerImpl在接受到数据后再调用接口,将改变的数据传递给PhoneStatusBarPolicy进行图标的显示与修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116//PhoneStatusBarPolicy @VisibleForTesting public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) { ... mBluetooth = Dependency.get(BluetoothController.class); ... updateBluetooth(); ... mBluetooth.addCallback(this); } //BluetoothControllerImpl public BluetoothControllerImpl(Context context, Looper bgLooper) { mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class); mBgHandler = new Handler(bgLooper); if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler); //添加监听 mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); onBluetoothStateChanged( mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState()); } mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mCurrentUser = ActivityManager.getCurrentUser(); } @Override public void onBluetoothStateChanged(int bluetoothState) { mEnabled = bluetoothState == BluetoothAdapter.STATE_ON || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; if (DEBUG) { Log.d(TAG, "onBluetoothStateChanged, bluetoothState = " + bluetoothState + ", mEnabled = " + mEnabled); } mState = bluetoothState; updateConnected(); //调用接口 mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } private final class H extends Handler { private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>(); private static final int MSG_PAIRED_DEVICES_CHANGED = 1; private static final int MSG_STATE_CHANGED = 2; private static final int MSG_ADD_CALLBACK = 3; private static final int MSG_REMOVE_CALLBACK = 4; public H(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { ... case MSG_STATE_CHANGED: fireStateChange(); break; case MSG_ADD_CALLBACK: mCallbacks.add((BluetoothController.Callback) msg.obj); break; case MSG_REMOVE_CALLBACK: mCallbacks.remove((BluetoothController.Callback) msg.obj); break; } } private void fireStateChange() { for (BluetoothController.Callback cb : mCallbacks) { fireStateChange(cb); } } private void fireStateChange(BluetoothController.Callback cb) { //调用接口 cb.onBluetoothStateChange(mEnabled); } } //BluetoothEventManager //添加监听 public void registerCallback(BluetoothCallback callback) { synchronized (mCallbacks) { mCallbacks.add(callback); } } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); BluetoothDevice device = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Handler handler = mHandlerMap.get(action); if (handler != null) { handler.onReceive(context, intent, device); } } }; private class AdapterStateChangedHandler implements Handler { public void onReceive(Context context, Intent intent, BluetoothDevice device) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); // Reregister Profile Broadcast Receiver as part of TURN OFF if (state == BluetoothAdapter.STATE_OFF) { context.unregisterReceiver(mProfileBroadcastReceiver); registerProfileIntentReceiver(); } // update local profiles and get paired devices mLocalAdapter.setBluetoothStateInt(state); // send callback to update UI and possibly start scanning synchronized (mCallbacks) { for (BluetoothCallback callback : mCallbacks) { //调用接口 callback.onBluetoothStateChanged(state); } } // Inform CachedDeviceManager that the adapter state has changed mDeviceManager.onBluetoothStateChanged(state); } }
我们看上面的逻辑可以发现一个问题,BluetoothControllerImpl添加接口和调用接口都是再Handler里进行,为什么?因为线程问题,我们都知道耗时操作在子线程中进行,而UI的修改必须在主线程中进行,所以需要通过Handler跳回主线程,所以在添加Tile的时候需要特别注意一下
接下来说一下QS的编辑功能,QS编辑页面是一个自定义的LinearLayout控件,页面布局是freeme_qs_customize_panel_content
编辑按钮是在qs_footer_impl.xml布局中,点击事件是在QSFooterImpl中设置,点击后会调用QSPanel中showEdit方法,再触发QSCustomizer中show方法,然后进行数据加载及页面加载
QS编辑页面的列表是一个RecyclerView,数据是在TileQueryHelper中的addStockTiles方法获取源码中添加的Tile,addPackageTiles方法获取通过TileService添加的Tile,通过TileAdapter加载列表
拖动功能则是通过ItemTouchHelper来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() { ... @Override public void onSelectedChanged(ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); //判断当前是长按选择还是松开 if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) { viewHolder = null; } if (viewHolder == mCurrentDrag) return; if (mCurrentDrag != null) { int position = mCurrentDrag.getAdapterPosition(); TileInfo info = mTiles.get(position); mCurrentDrag.mTileView.setShowAppLabel( position > mEditIndex && !info.isSystem); //Tile松开动画 mCurrentDrag.stopDrag(); mCurrentDrag = null; } if (viewHolder != null) { mCurrentDrag = (Holder) viewHolder; //Tile长按选中动画 mCurrentDrag.startDrag(); } mHandler.post(new Runnable() { @Override public void run() { //刷新页面,主要是刷新提示语句 notifyItemChanged(mEditIndex); } }); } @Override public boolean canDropOver(RecyclerView recyclerView, ViewHolder current, ViewHolder target) { //判断是否可以交换位置 if (!canRemoveTiles() && current.getAdapterPosition() < mEditIndex) { return target.getAdapterPosition() < mEditIndex; } return target.getAdapterPosition() <= mEditIndex + 1; } @Override public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) { //设置ViewHolder的拖动和滑动方向 if (viewHolder.getItemViewType() == TYPE_EDIT || viewHolder.getItemViewType() == TYPE_DIVIDER) { return makeMovementFlags(0, 0); } int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT; return makeMovementFlags(dragFlags, 0); } @Override public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) { //两个viewHolder交换时触发 int from = viewHolder.getAdapterPosition(); int to = target.getAdapterPosition(); return move(from, to, target.itemView); } ... };
当我们编辑完成后会执行TileAdapter中的saveSpecs方法进行保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//TileAdapter public void saveSpecs(QSTileHost host) { List<String> newSpecs = new ArrayList<>(); for (int i = 0; i < mTiles.size() && mTiles.get(i) != null; i++) { newSpecs.add(mTiles.get(i).spec); } host.changeTiles(mCurrentSpecs, newSpecs); mCurrentSpecs = newSpecs; } //QSTileHost public void changeTiles(List<String> previousTiles, List<String> newTiles) { ... if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); //保存到SettingsProvider中 Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING, TextUtils.join(",", newTiles), ActivityManager.getCurrentUser()); }
在上面我们说过执行addTunable时会注册一个内容观察者,每此保存数据后都会触发内容观察者,执行reloadSetting方法刷新QS快捷面板
最后
以上就是美满月饼最近收集整理的关于SystemUI学习总结(二)的全部内容,更多相关SystemUI学习总结(二)内容请搜索靠谱客的其他文章。
发表评论 取消回复