Android SystemUI之下拉菜单,通知栏,快捷面板(三) 您所在的位置:网站首页 Android隐藏通知栏 Android SystemUI之下拉菜单,通知栏,快捷面板(三)

Android SystemUI之下拉菜单,通知栏,快捷面板(三)

2023-07-13 07:32| 来源: 网络整理| 查看: 265

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

这个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

 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 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 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 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 tiles = new ArrayList(); 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 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 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 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 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()); } }

所以到现在我们的下拉菜单中的快捷面板的加载流程已经加载完成。至于短信的加载流程暂时不讲,等以后有时间再研究。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有