Android SystemUI系列:
1.Android SystemUI之启动流程(一)
2.Android SystemUI之StatusBar,状态栏(二)
3.Android SystemUI之下拉菜单,通知栏,快捷面板(三)
4.Android SystemUI之NavigationBar,导航栏(四)
5.Android SystemUI之Recent,近期列表(五)
一、下拉菜单创建流程
在上一个博文(Android SystemUI之StatusBar,状态栏(二))的开篇有给出一个图,里面描述了StatusBar的设备树。super_status_bar会分两个分支一个是状态栏,这个上个博文已经讲了,另一个就是下拉菜单,QS面板。也是本博文需要讲解的。
在说下拉菜单创建的过程我们先看两副图
下拉菜单两种不同的布局,现在我们就来好好分析这两个布局的创建流程。
1.QSFragment的创建
status_bar_expanded.xml
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" >
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:id="@+id/notification_container_parent"
android:clipToPadding="false"
android:clipChildren="false">
<!--
layout_gravity="@integer/notification_panel_layout_gravit-> center_horizontal|top
-->
<FrameLayout
android:id="@+id/qs_frame"
android:layout="@layout/qs_panel"
android:layout_width="@dimen/qs_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:clipToPadding="false"
android:clipChildren="false"
systemui:viewType="com.android.systemui.plugins.qs.QS" />
<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap" />
<include layout="@layout/ambient_indication"
android:id="@+id/ambient_indication_container" />
<ViewStub
android:id="@+id/keyguard_user_switcher"
android:layout="@layout/keyguard_user_switcher"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
<Button
android:id="@+id/report_rejected_touch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
android:text="@string/report_rejected_touch"
android:visibility="gone" />
</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
<com.android.systemui.statusbar.AlphaOptimizedView
android:id="@+id/qs_navbar_scrim"
android:layout_height="96dp"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:visibility="invisible"
android:background="@drawable/qs_navbar_scrim" />
</com.android.systemui.statusbar.phone.NotificationPanelView>
这个status_bar_expanded.xml就是下拉菜单的布局文件。里面包含的View很多,我们主要看以下几个:
1.@layout/keyguard_status_view 这个是锁屏界面的View
2.@+id/qs_frame QS快捷面板
3.@+id/notification_stack_scroller短信通知栏
在StatusBar有如下这段代码,这样@+id/qs_frame的界面的控制就被转移到QSFragment,相应的layout也就变成了qs_panel
//快捷面板
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
Dependency.get(ExtensionController.class)
.newExtension(QS.class)
.withPlugin(QS.class)
.withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
.withDefault(QSFragment::new)
.build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
((QSFragment) qs).setHost(qsh);
mQSPanel = ((QSFragment) qs).getQsPanel();
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mKeyguardStatusBar.setQSPanel(mQSPanel);
}
});
}
2.qs_panel
<com.android.systemui.qs.QSContainerImpl
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:clipChildren="false" >
<!-- Main QS background -->
<View
android:id="@+id/quick_settings_background"
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="4dp"
android:background="@drawable/qs_background_primary" />
<!-- Black part behind the status bar -->
<View
android:id="@+id/quick_settings_status_bar_background"
android:layout_width="match_parent"
android:layout_height="@*android:dimen/quick_qs_offset_height"
android:clipToPadding="false"
android:clipChildren="false"
android:background="#ff000000" />
<!-- Gradient view behind QS -->
<View
android:id="@+id/quick_settings_gradient_view"
android:layout_width="match_parent"
android:layout_height="126dp"
android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
android:clipToPadding="false"
android:clipChildren="false"
android:background="@drawable/qs_bg_gradient" />
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/qs_footer_height"
android:elevation="4dp"
android:background="@android:color/transparent"
android:focusable="true"
android:accessibilityTraversalBefore="@id/qs_carrier_text"
/>
<include layout="@layout/quick_status_bar_expanded_header" />
<include layout="@layout/qs_footer_impl" />
<include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
</com.android.systemui.qs.QSContainerImpl>
1.@+id/quick_settings_panel 这个就是快捷面板容器,布局风格对应我们开篇说的第一幅图。
2.@layout/quick_status_bar_expanded_header 这个layout也包含了一个快捷面板,布局风格对应我们开篇说的第二幅图
3.在上面的代码中有一行 ((QSFragment) qs).setHost(qsh),那我们来看看QSFragment.setHost是做什么的
public void setHost(QSTileHost qsh) {
mQSPanel.setHost(qsh, mQSCustomizer);
mHeader.setQSPanel(mQSPanel);
mFooter.setQSPanel(mQSPanel);
mQSDetail.setHost(qsh);
if (mQSAnimator != null) {
mQSAnimator.setHost(qsh);
}
}
4. mQSPanel.setHost
public void setHost(QSTileHost host, QSCustomizer customizer) {
mHost = host;
mHost.addCallback(this);
setTiles(mHost.getTiles());
mFooter.setHostEnvironment(host);
mCustomizePanel = customizer;
if (mCustomizePanel != null) {
mCustomizePanel.setHost(mHost);
}
mQuickSettingsExt.setHostAppInstance(host);
}
setTiles(mHost.getTiles())这个就是快捷面板添加的入口,那思考一下,mHost.getTiles()这个数据是从哪里获取的呢?
答案是:QSTileHost.onTuningChanged
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
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);
}
}
}
mCurrentUser = currentUser;
mTileSpecs.clear();
mTileSpecs.addAll(tileSpecs);
mTiles.clear();
mTiles.putAll(newTiles);
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
}
protected List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
/// M: Customize the quick settings tile order for operator. @{
if (mQuickSettingsExt != null) {
defaultTileList = mQuickSettingsExt.addOpTileSpecs(defaultTileList);
// @}
defaultTileList = mQuickSettingsExt.customizeQuickSettingsTileOrder(defaultTileList);
}
if(StatusBar.SYSTEMUI_NOTIFICATION_DEBUG)Log.i(StatusBar.TAG_XIAO,"QSTilHost loadTileSpecs defaultTileList:"+ 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;
}
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;
}
}
// M: @ {
if (mQuickSettingsExt != null && mQuickSettingsExt.doOperatorSupportTile(tileSpec)) {
// WifiCalling
return (QSTile) mQuickSettingsExt.createTile(this, tileSpec);
}
// @ }
return null;
}
QSFactoryImpl.createTile
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);
}
/// M: Customize the quick settings tiles for operator. @{
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));
}
/// @}
// Intent tiles.
if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);
// Debug tiles.
if (Build.IS_DEBUGGABLE) {
if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) {
return new GarbageMonitor.MemoryTile(mHost);
}
}
// Broken tiles.
Log.w(TAG, "Bad tile spec: " + tileSpec);
return null;
}
onTuningChanged这个函数的创建比QSFragment更早,所以当我们调用mHost.getTiles()时,数据就已经准备好了。在loadTileSpecs函数里面有这一行 String defaultTileList = res.getString(R.string.quick_settings_tiles_default)获取我们需要加载在快捷面板上面的项目。依据defaultTileList 来createTile(tileSpec)创建对应的QSTile。至此数据创建完毕,后续就是数据的使用
5.既然数据获取流程已经知道,那我们来看看setTiles的工作
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
if (!collapsedView) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
//先清空再加载
for (TileRecord record : mRecords) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
}
mRecords.clear();
for (QSTile tile : tiles) {
addTile(tile, collapsedView);
}
}
protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = createTileView(tile, collapsedView);
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
drawTile(r, state);
}
@Override
public void onShowDetail(boolean show) {
// Both the collapsed and full QS panels get this callback, this check determines
// which one should handle showing the detail.
if (shouldShowDetail()) {
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) {
if (announcement != null) {
mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
.sendToTarget();
}
}
};
r.tile.addCallback(callback);
r.callback = callback;
r.tileView.init(r.tile);
r.tile.refreshState();
mRecords.add(r);
if (mTileLayout != null) {
mTileLayout.addTile(r);//加载到页面上
}
return r;
}
protected QSTileView createTileView(QSTile tile, boolean collapsedView) {
return mHost.createTileView(tile, collapsedView);
}
public QSTileView createTileView(QSTile tile, boolean collapsedView) {
for (int i = 0; i < mQsFactories.size(); i++) {
QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
if (view != null) {
return view;
}
}
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
}
QSFactoryImpl.createTileView
public QSTileView createTileView(QSTile tile, boolean collapsedView) {
Context context = new ContextThemeWrapper(mHost.getContext(), R.style.qs_theme);
QSIconView icon = tile.createTileView(context);
if (collapsedView) {
return new QSTileBaseView(context, icon, collapsedView);
} else {
return new com.android.systemui.qs.tileimpl.QSTileView(context, icon);
}
}
这段代码的工作有两个:1.由tile的数据创建QSTileView,并且保持在TileRecord。
2.把创建好的TileRecord 添加的快捷面板中 mTileLayout.addTile(r)。
二、快捷面板的加载
mTileLayout是什么?先看下面一段代码
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setOrientation(VERTICAL);//设置竖直方向
mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
addView(mBrightnessView);
// M: @ {
mQuickSettingsExt = OpSystemUICustomizationFactoryBase
.getOpFactory(context).makeQuickSettings(context);
if (mQuickSettingsExt != null) {
mQuickSettingsExt.addOpViews(this);
}
// @ }
mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
R.layout.qs_paged_tile_layout, this, false);
mTileLayout.setListening(mListening);
addView((View) mTileLayout);
mPanelPageIndicator = (PageIndicator) LayoutInflater.from(context).inflate(
R.layout.qs_page_indicator, this, false);
addView(mPanelPageIndicator);
((PagedTileLayout) mTileLayout).setPageIndicator(mPanelPageIndicator);
mQsTileRevealController = new QSTileRevealController(mContext, this,
(PagedTileLayout) mTileLayout);
addDivider();
mFooter = new QSSecurityFooter(this, context);
addView(mFooter.getView());
updateResources();
mBrightnessController = new BrightnessController(getContext(),
findViewById(R.id.brightness_icon),
findViewById(R.id.brightness_slider));
}
从上面代码我们看两个关键信息:1.亮度调节显示控件在此被动态加载成功mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
2.mTileLayout 其实是由qs_paged_tile_layout的layout加载而来,mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( R.layout.qs_paged_tile_layout, this, false);
qs_paged_tile_layout.xml
<com.android.systemui.qs.PagedTileLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom">
<FrameLayout
android:id="@+id/page_decor"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="bottom">
</FrameLayout>
</com.android.systemui.qs.PagedTileLayout>
public class PagedTileLayout extends ViewPager implements QSTileLayout
PagedTileLayout是一个ViewPager,我们熟悉ViewPager的用法,那么我们也就不难推断出PagedTileLayout的一些特性。比如滑动翻页。
继续之前mTileLayout.addTile(r)的代码研究
PagedTileLayout.addTile
public void addTile(TileRecord tile) {
mTiles.add(tile);
postDistributeTiles();
}
private final ArrayList<TileRecord> mTiles = new ArrayList<>();是一个list,就是把数据保存起来
private void postDistributeTiles() {
removeCallbacks(mDistribute);
post(mDistribute);
}
private final Runnable mDistribute = new Runnable() {
@Override
public void run() {
distributeTiles();
}
};
private void distributeTiles() {
if (DEBUG) Log.d(TAG, "Distributing tiles");
final int NP = mPages.size();
for (int i = 0; i < NP; i++) {
mPages.get(i).removeAllViews();
}
int index = 0;
final int NT = mTiles.size();
for (int i = 0; i < NT; i++) {
TileRecord tile = mTiles.get(i);
if (mPages.get(index).isFull()) {
if (++index == mPages.size()) {
if (DEBUG) Log.d(TAG, "Adding page for "
+ tile.tile.getClass().getSimpleName());
mPages.add((TilePage) LayoutInflater.from(getContext())
.inflate(R.layout.qs_paged_page, this, false));
}
}
if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
+ index);
mPages.get(index).addTile(tile);
}
if (mNumPages != index + 1) {
mNumPages = index + 1;
while (mPages.size() > mNumPages) {
mPages.remove(mPages.size() - 1);
}
if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
mPageIndicator.setNumPages(mNumPages);
setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
setCurrentItem(0, false);
}
}
上面的代码中有一个比较关键的 mPages.get(index).addTile(tile);,
mPages是 private final ArrayList<TilePage> mPages = new ArrayList<>();
由于index=0.这也就导致只有第一页才会加载所有的快捷图标,实际我们使用也是这样的。
那我们来看看TilePage是什么
public static class TilePage extends TileLayout {
private int mMaxRows = 3;
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();
}
@Override
public boolean updateResources() {
final int rows = getRows();
boolean changed = rows != mMaxRows;
if (changed) {
mMaxRows = rows;
requestLayout();
}
return super.updateResources() || changed;
}
private int getRows() {
//快捷面板显示的行数
return Math.max(1, getResources().getInteger(R.integer.quick_settings_num_rows));
}
public void setMaxRows(int maxRows) {
mMaxRows = maxRows;
}
public boolean isFull() {
return mRecords.size() >= mColumns * mMaxRows;
}
}
继承了TileLayout,并且对于滑动的页数是由getRows()来决定的
TileLayout 我们看三个函数,因为这三个函数跟快捷按键的图标大小位置有关。
1.updateResources 会初始化一些值,比如高度和边距等。
2.onMeasure 主要确定TileLayout的宽度和高度,也就是快捷面板的宽高,并且确定子View的宽高
3.onLayout确定子View的布局排列,是一行排三个还是一行排四个,都是在onLayout里面来实现。
捷面板里面的每个单元格图标的显示大小以及间隔
public boolean updateResources() {
final Resources res = mContext.getResources();
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
if (mColumns != columns) {
mColumns = columns;
requestLayout();
return true;
}
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int numTiles = mRecords.size();
final int width = MeasureSpec.getSize(widthMeasureSpec)
- getPaddingStart() - getPaddingEnd();
final int numRows = (numTiles + mColumns - 1) / mColumns;//每行的数量
mCellWidth = (width - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns;//每个图标的宽度
// Measure each QS tile.
View previousView = this;
for (TileRecord record : mRecords) {
if (record.tileView.getVisibility() == GONE) continue;
record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));//测量子View的宽度和高度
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
// Only include the top margin in our measurement if we have more than 1 row to show.
// Otherwise, don't add the extra margin buffer at top.
//最后计算出高度大小,
int height = (mCellHeight + mCellMarginVertical) * numRows +
(numRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0);
if (height < 0) height = 0;
setMeasuredDimension(width, height);
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getWidth();
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int row = 0;
int column = 0;
// Layout each QS tile.
//最后决定子View的位置在哪里
for (int i = 0; i < mRecords.size(); i++, column++) {
// If we reached the last column available to layout a tile, wrap back to the next row.
if (column == mColumns) {
column = 0;
row++;
}
final TileRecord record = mRecords.get(i);
final int top = getRowTop(row);
final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
final int right = left + mCellWidth;
record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
}
}
所以到现在我们的下拉菜单中的快捷面板的加载流程已经加载完成。至于短信的加载流程暂时不讲,等以后有时间再研究。