我是靠谱客的博主 美满月饼,最近开发中收集的这篇文章主要介绍SystemUI学习总结(二),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

上次我们研究了常态显示下的状态栏,这篇我们来研究下拉后状态栏,页面是status_bar_expanded.xml
我们将下拉后的状态栏拆分来看,首先看QS快捷控制面板
关于QS快捷键我们可以分为两个类型stock和tileservice,stock是在源码中进行添加,tileService则是android7.0时谷歌添加的一个专门可以将第三方应用显示在QS快捷面板中的api,类似红包助手之类的,跟第三应用添加到设置中逻辑大致类似,通过在activity在清单文件中配置对应的action来搜索获取数据,然后控制点击事件和其它一些内容则是继承了TileService的类
stock类型的数据获取是在QSTileHost中获取,QSTileHost在创建的时候会执行TunerServiceImpl类中的addTunable方法,我们看一下这个方法的源码

//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类里

public 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为例,标签和图标是在

 @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中控制

	public 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中

	public 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中的内容

	//点击事件
@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中

@Override
public void setBluetoothEnabled(boolean enabled) {
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
}
}

蓝牙的流程是通过控制器获取adapter然后再将值传入IBander进行跨进程通信,QS快捷面板跟设置进行互通最主要的就是IBander,其它的只是辅助的内容
与任务栏中的图标互通,首先PhoneStatusBarPolicy实现了蓝牙的接口,并且进行添加,然后BluetoothEventManager里通过广播监听蓝牙的改变,然后将改变后的数据通过接口传递给BluetoothControllerImpl,BluetoothControllerImpl在创建的时候就实现了接口并添加,BluetoothControllerImpl在接受到数据后再调用接口,将改变的数据传递给PhoneStatusBarPolicy进行图标的显示与修改

//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来实现

private 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方法进行保存

//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学习总结(二)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部