当前位置:   article > 正文

Android SystemUI之下拉菜单,通知栏,快捷面板(三)_com.android.systemui:id/notification_stack_scrolle

com.android.systemui:id/notification_stack_scroller

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

  1. <com.android.systemui.statusbar.phone.NotificationPanelView
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  4. android:id="@+id/notification_panel"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:background="@android:color/transparent" >
  8. <include
  9. layout="@layout/keyguard_status_view"
  10. android:visibility="gone" />
  11. <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"
  14. android:layout_gravity="@integer/notification_panel_layout_gravity"
  15. android:id="@+id/notification_container_parent"
  16. android:clipToPadding="false"
  17. android:clipChildren="false">
  18. <!--
  19. layout_gravity="@integer/notification_panel_layout_gravit-> center_horizontal|top
  20. -->
  21. <FrameLayout
  22. android:id="@+id/qs_frame"
  23. android:layout="@layout/qs_panel"
  24. android:layout_width="@dimen/qs_panel_width"
  25. android:layout_height="match_parent"
  26. android:layout_gravity="@integer/notification_panel_layout_gravity"
  27. android:clipToPadding="false"
  28. android:clipChildren="false"
  29. systemui:viewType="com.android.systemui.plugins.qs.QS" />
  30. <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
  31. android:id="@+id/notification_stack_scroller"
  32. android:layout_marginTop="@dimen/notification_panel_margin_top"
  33. android:layout_width="@dimen/notification_panel_width"
  34. android:layout_height="match_parent"
  35. android:layout_gravity="@integer/notification_panel_layout_gravity"
  36. android:layout_marginBottom="@dimen/close_handle_underlap" />
  37. <include layout="@layout/ambient_indication"
  38. android:id="@+id/ambient_indication_container" />
  39. <ViewStub
  40. android:id="@+id/keyguard_user_switcher"
  41. android:layout="@layout/keyguard_user_switcher"
  42. android:layout_height="match_parent"
  43. android:layout_width="match_parent" />
  44. <include
  45. layout="@layout/keyguard_status_bar"
  46. android:visibility="invisible" />
  47. <Button
  48. android:id="@+id/report_rejected_touch"
  49. android:layout_width="wrap_content"
  50. android:layout_height="wrap_content"
  51. android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
  52. android:text="@string/report_rejected_touch"
  53. android:visibility="gone" />
  54. </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
  55. <include
  56. layout="@layout/keyguard_bottom_area"
  57. android:visibility="gone" />
  58. <com.android.systemui.statusbar.AlphaOptimizedView
  59. android:id="@+id/qs_navbar_scrim"
  60. android:layout_height="96dp"
  61. android:layout_width="match_parent"
  62. android:layout_gravity="bottom"
  63. android:visibility="invisible"
  64. android:background="@drawable/qs_navbar_scrim" />
  65. </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

  1. //快捷面板
  2. View container = mStatusBarWindow.findViewById(R.id.qs_frame);
  3. if (container != null) {
  4. FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
  5. ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
  6. Dependency.get(ExtensionController.class)
  7. .newExtension(QS.class)
  8. .withPlugin(QS.class)
  9. .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
  10. .withDefault(QSFragment::new)
  11. .build());
  12. final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
  13. mIconController);
  14. mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
  15. (visible) -> {
  16. mBrightnessMirrorVisible = visible;
  17. updateScrimController();
  18. });
  19. fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
  20. QS qs = (QS) f;
  21. if (qs instanceof QSFragment) {
  22. ((QSFragment) qs).setHost(qsh);
  23. mQSPanel = ((QSFragment) qs).getQsPanel();
  24. mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
  25. mKeyguardStatusBar.setQSPanel(mQSPanel);
  26. }
  27. });
  28. }

2.qs_panel

  1. <com.android.systemui.qs.QSContainerImpl
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/quick_settings_container"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. android:clipToPadding="false"
  7. android:clipChildren="false" >
  8. <!-- Main QS background -->
  9. <View
  10. android:id="@+id/quick_settings_background"
  11. android:layout_width="match_parent"
  12. android:layout_height="0dp"
  13. android:elevation="4dp"
  14. android:background="@drawable/qs_background_primary" />
  15. <!-- Black part behind the status bar -->
  16. <View
  17. android:id="@+id/quick_settings_status_bar_background"
  18. android:layout_width="match_parent"
  19. android:layout_height="@*android:dimen/quick_qs_offset_height"
  20. android:clipToPadding="false"
  21. android:clipChildren="false"
  22. android:background="#ff000000" />
  23. <!-- Gradient view behind QS -->
  24. <View
  25. android:id="@+id/quick_settings_gradient_view"
  26. android:layout_width="match_parent"
  27. android:layout_height="126dp"
  28. android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
  29. android:clipToPadding="false"
  30. android:clipChildren="false"
  31. android:background="@drawable/qs_bg_gradient" />
  32. <com.android.systemui.qs.QSPanel
  33. android:id="@+id/quick_settings_panel"
  34. android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
  35. android:layout_width="match_parent"
  36. android:layout_height="wrap_content"
  37. android:layout_marginBottom="@dimen/qs_footer_height"
  38. android:elevation="4dp"
  39. android:background="@android:color/transparent"
  40. android:focusable="true"
  41. android:accessibilityTraversalBefore="@id/qs_carrier_text"
  42. />
  43. <include layout="@layout/quick_status_bar_expanded_header" />
  44. <include layout="@layout/qs_footer_impl" />
  45. <include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
  46. <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
  47. android:visibility="gone" />
  48. </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是做什么的

  1. public void setHost(QSTileHost qsh) {
  2. mQSPanel.setHost(qsh, mQSCustomizer);
  3. mHeader.setQSPanel(mQSPanel);
  4. mFooter.setQSPanel(mQSPanel);
  5. mQSDetail.setHost(qsh);
  6. if (mQSAnimator != null) {
  7. mQSAnimator.setHost(qsh);
  8. }
  9. }

4. mQSPanel.setHost

  1. public void setHost(QSTileHost host, QSCustomizer customizer) {
  2. mHost = host;
  3. mHost.addCallback(this);
  4. setTiles(mHost.getTiles());
  5. mFooter.setHostEnvironment(host);
  6. mCustomizePanel = customizer;
  7. if (mCustomizePanel != null) {
  8. mCustomizePanel.setHost(mHost);
  9. }
  10. mQuickSettingsExt.setHostAppInstance(host);
  11. }

  setTiles(mHost.getTiles())这个就是快捷面板添加的入口,那思考一下,mHost.getTiles()这个数据是从哪里获取的呢?

答案是:QSTileHost.onTuningChanged

  1. public void onTuningChanged(String key, String newValue) {
  2. if (!TILES_SETTING.equals(key)) {
  3. return;
  4. }
  5. if (DEBUG) Log.d(TAG, "Recreating tiles");
  6. if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
  7. newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
  8. }
  9. final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
  10. int currentUser = ActivityManager.getCurrentUser();
  11. if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
  12. mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
  13. tile -> {
  14. if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
  15. tile.getValue().destroy();
  16. });
  17. final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
  18. for (String tileSpec : tileSpecs) {
  19. QSTile tile = mTiles.get(tileSpec);
  20. if (tile != null && (!(tile instanceof CustomTile)
  21. || ((CustomTile) tile).getUser() == currentUser)) {
  22. if (tile.isAvailable()) {
  23. if (DEBUG) Log.d(TAG, "Adding " + tile);
  24. tile.removeCallbacks();
  25. if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
  26. tile.userSwitch(currentUser);
  27. }
  28. newTiles.put(tileSpec, tile);
  29. } else {
  30. tile.destroy();
  31. }
  32. } else {
  33. if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
  34. try {
  35. tile = createTile(tileSpec);
  36. if (tile != null) {
  37. if (tile.isAvailable()) {
  38. tile.setTileSpec(tileSpec);
  39. newTiles.put(tileSpec, tile);
  40. } else {
  41. tile.destroy();
  42. }
  43. }
  44. } catch (Throwable t) {
  45. Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
  46. }
  47. }
  48. }
  49. mCurrentUser = currentUser;
  50. mTileSpecs.clear();
  51. mTileSpecs.addAll(tileSpecs);
  52. mTiles.clear();
  53. mTiles.putAll(newTiles);
  54. for (int i = 0; i < mCallbacks.size(); i++) {
  55. mCallbacks.get(i).onTilesChanged();
  56. }
  57. }
  1. protected List<String> loadTileSpecs(Context context, String tileList) {
  2. final Resources res = context.getResources();
  3. String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
  4. /// M: Customize the quick settings tile order for operator. @{
  5. if (mQuickSettingsExt != null) {
  6. defaultTileList = mQuickSettingsExt.addOpTileSpecs(defaultTileList);
  7. // @}
  8. defaultTileList = mQuickSettingsExt.customizeQuickSettingsTileOrder(defaultTileList);
  9. }
  10. if(StatusBar.SYSTEMUI_NOTIFICATION_DEBUG)Log.i(StatusBar.TAG_XIAO,"QSTilHost loadTileSpecs defaultTileList:"+ defaultTileList);
  11. /// M: Customize the quick settings tile order for operator. @}
  12. Log.d(TAG, "loadTileSpecs() default tile list: " + defaultTileList);
  13. if (tileList == null) {
  14. tileList = res.getString(R.string.quick_settings_tiles);
  15. if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
  16. } else {
  17. if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
  18. }
  19. final ArrayList<String> tiles = new ArrayList<String>();
  20. boolean addedDefault = false;
  21. for (String tile : tileList.split(",")) {
  22. tile = tile.trim();
  23. if (tile.isEmpty()) continue;
  24. if (tile.equals("default")) {
  25. if (!addedDefault) {
  26. tiles.addAll(Arrays.asList(defaultTileList.split(",")));
  27. addedDefault = true;
  28. }
  29. } else {
  30. tiles.add(tile);
  31. }
  32. }
  33. return tiles;
  34. }
  1. public QSTile createTile(String tileSpec) {
  2. for (int i = 0; i < mQsFactories.size(); i++) {
  3. QSTile t = mQsFactories.get(i).createTile(tileSpec);
  4. if (t != null) {
  5. return t;
  6. }
  7. }
  8. // M: @ {
  9. if (mQuickSettingsExt != null && mQuickSettingsExt.doOperatorSupportTile(tileSpec)) {
  10. // WifiCalling
  11. return (QSTile) mQuickSettingsExt.createTile(this, tileSpec);
  12. }
  13. // @ }
  14. return null;
  15. }

 QSFactoryImpl.createTile

  1. public QSTile createTile(String tileSpec) {
  2. QSTileImpl tile = createTileInternal(tileSpec);
  3. if (tile != null) {
  4. tile.handleStale(); // Tile was just created, must be stale.
  5. }
  6. return tile;
  7. }
  8. private QSTileImpl createTileInternal(String tileSpec) {
  9. /// M: Add extra tiles in quicksetting @{
  10. Context context = mHost.getContext();
  11. IQuickSettingsPlugin quickSettingsPlugin = OpSystemUICustomizationFactoryBase
  12. .getOpFactory(context).makeQuickSettings(context);
  13. /// @}
  14. // Stock tiles.
  15. switch (tileSpec) {
  16. case "wifi":
  17. return new WifiTile(mHost);
  18. case "bt":
  19. return new BluetoothTile(mHost);
  20. case "cell":
  21. return new CellularTile(mHost);
  22. case "dnd":
  23. return new DndTile(mHost);
  24. case "inversion":
  25. return new ColorInversionTile(mHost);
  26. case "airplane":
  27. return new AirplaneModeTile(mHost);
  28. case "work":
  29. return new WorkModeTile(mHost);
  30. case "rotation":
  31. return new RotationLockTile(mHost);
  32. case "flashlight":
  33. return new FlashlightTile(mHost);
  34. case "location":
  35. return new LocationTile(mHost);
  36. case "cast":
  37. return new CastTile(mHost);
  38. case "hotspot":
  39. return new HotspotTile(mHost);
  40. case "user":
  41. return new UserTile(mHost);
  42. case "battery":
  43. return new BatterySaverTile(mHost);
  44. case "saver":
  45. return new DataSaverTile(mHost);
  46. case "night":
  47. return new NightDisplayTile(mHost);
  48. case "nfc":
  49. return new NfcTile(mHost);
  50. }
  51. /// M: Customize the quick settings tiles for operator. @{
  52. if (tileSpec.equals("dataconnection") && !SIMHelper.isWifiOnlyDevice())
  53. return new MobileDataTile(mHost);
  54. else if (tileSpec.equals("simdataconnection") && !SIMHelper.isWifiOnlyDevice() &&
  55. quickSettingsPlugin.customizeAddQSTile(new SimDataConnectionTile(mHost)) != null) {
  56. return (SimDataConnectionTile) quickSettingsPlugin.customizeAddQSTile(
  57. new SimDataConnectionTile(mHost));
  58. } else if (tileSpec.equals("dulsimsettings") && !SIMHelper.isWifiOnlyDevice() &&
  59. quickSettingsPlugin.customizeAddQSTile(new DualSimSettingsTile(mHost)) != null) {
  60. return (DualSimSettingsTile) quickSettingsPlugin.customizeAddQSTile(
  61. new DualSimSettingsTile(mHost));
  62. } else if (tileSpec.equals("apnsettings") && !SIMHelper.isWifiOnlyDevice() &&
  63. quickSettingsPlugin.customizeAddQSTile(new ApnSettingsTile(mHost)) != null) {
  64. return (ApnSettingsTile) quickSettingsPlugin.customizeAddQSTile(
  65. new ApnSettingsTile(mHost));
  66. }
  67. /// @}
  68. // Intent tiles.
  69. if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
  70. if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);
  71. // Debug tiles.
  72. if (Build.IS_DEBUGGABLE) {
  73. if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) {
  74. return new GarbageMonitor.MemoryTile(mHost);
  75. }
  76. }
  77. // Broken tiles.
  78. Log.w(TAG, "Bad tile spec: " + tileSpec);
  79. return null;
  80. }

 

 onTuningChanged这个函数的创建比QSFragment更早,所以当我们调用mHost.getTiles()时,数据就已经准备好了。在loadTileSpecs函数里面有这一行 String defaultTileList = res.getString(R.string.quick_settings_tiles_default)获取我们需要加载在快捷面板上面的项目。依据defaultTileList 来createTile(tileSpec)创建对应的QSTile。至此数据创建完毕,后续就是数据的使用

5.既然数据获取流程已经知道,那我们来看看setTiles的工作

  1. public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
  2. if (!collapsedView) {
  3. mQsTileRevealController.updateRevealedTiles(tiles);
  4. }
  5. //先清空再加载
  6. for (TileRecord record : mRecords) {
  7. mTileLayout.removeTile(record);
  8. record.tile.removeCallback(record.callback);
  9. }
  10. mRecords.clear();
  11. for (QSTile tile : tiles) {
  12. addTile(tile, collapsedView);
  13. }
  14. }
  1. protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
  2. final TileRecord r = new TileRecord();
  3. r.tile = tile;
  4. r.tileView = createTileView(tile, collapsedView);
  5. final QSTile.Callback callback = new QSTile.Callback() {
  6. @Override
  7. public void onStateChanged(QSTile.State state) {
  8. drawTile(r, state);
  9. }
  10. @Override
  11. public void onShowDetail(boolean show) {
  12. // Both the collapsed and full QS panels get this callback, this check determines
  13. // which one should handle showing the detail.
  14. if (shouldShowDetail()) {
  15. QSPanel.this.showDetail(show, r);
  16. }
  17. }
  18. @Override
  19. public void onToggleStateChanged(boolean state) {
  20. if (mDetailRecord == r) {
  21. fireToggleStateChanged(state);
  22. }
  23. }
  24. @Override
  25. public void onScanStateChanged(boolean state) {
  26. r.scanState = state;
  27. if (mDetailRecord == r) {
  28. fireScanStateChanged(r.scanState);
  29. }
  30. }
  31. @Override
  32. public void onAnnouncementRequested(CharSequence announcement) {
  33. if (announcement != null) {
  34. mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
  35. .sendToTarget();
  36. }
  37. }
  38. };
  39. r.tile.addCallback(callback);
  40. r.callback = callback;
  41. r.tileView.init(r.tile);
  42. r.tile.refreshState();
  43. mRecords.add(r);
  44. if (mTileLayout != null) {
  45. mTileLayout.addTile(r);//加载到页面上
  46. }
  47. return r;
  48. }
  1. protected QSTileView createTileView(QSTile tile, boolean collapsedView) {
  2. return mHost.createTileView(tile, collapsedView);
  3. }
  1. public QSTileView createTileView(QSTile tile, boolean collapsedView) {
  2. for (int i = 0; i < mQsFactories.size(); i++) {
  3. QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
  4. if (view != null) {
  5. return view;
  6. }
  7. }
  8. throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
  9. }

 QSFactoryImpl.createTileView

  1. public QSTileView createTileView(QSTile tile, boolean collapsedView) {
  2. Context context = new ContextThemeWrapper(mHost.getContext(), R.style.qs_theme);
  3. QSIconView icon = tile.createTileView(context);
  4. if (collapsedView) {
  5. return new QSTileBaseView(context, icon, collapsedView);
  6. } else {
  7. return new com.android.systemui.qs.tileimpl.QSTileView(context, icon);
  8. }
  9. }

 

这段代码的工作有两个:1.由tile的数据创建QSTileView,并且保持在TileRecord。

                                        2.把创建好的TileRecord 添加的快捷面板中 mTileLayout.addTile(r)。

二、快捷面板的加载

           mTileLayout是什么?先看下面一段代码

  1. public QSPanel(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. mContext = context;
  4. setOrientation(VERTICAL);//设置竖直方向
  5. mBrightnessView = LayoutInflater.from(mContext).inflate(
  6. R.layout.quick_settings_brightness_dialog, this, false);
  7. addView(mBrightnessView);
  8. // M: @ {
  9. mQuickSettingsExt = OpSystemUICustomizationFactoryBase
  10. .getOpFactory(context).makeQuickSettings(context);
  11. if (mQuickSettingsExt != null) {
  12. mQuickSettingsExt.addOpViews(this);
  13. }
  14. // @ }
  15. mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
  16. R.layout.qs_paged_tile_layout, this, false);
  17. mTileLayout.setListening(mListening);
  18. addView((View) mTileLayout);
  19. mPanelPageIndicator = (PageIndicator) LayoutInflater.from(context).inflate(
  20. R.layout.qs_page_indicator, this, false);
  21. addView(mPanelPageIndicator);
  22. ((PagedTileLayout) mTileLayout).setPageIndicator(mPanelPageIndicator);
  23. mQsTileRevealController = new QSTileRevealController(mContext, this,
  24. (PagedTileLayout) mTileLayout);
  25. addDivider();
  26. mFooter = new QSSecurityFooter(this, context);
  27. addView(mFooter.getView());
  28. updateResources();
  29. mBrightnessController = new BrightnessController(getContext(),
  30. findViewById(R.id.brightness_icon),
  31. findViewById(R.id.brightness_slider));
  32. }

从上面代码我们看两个关键信息: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

  1. <com.android.systemui.qs.PagedTileLayout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:clipChildren="false"
  6. android:clipToPadding="false"
  7. android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom">
  8. <FrameLayout
  9. android:id="@+id/page_decor"
  10. android:layout_width="wrap_content"
  11. android:layout_height="48dp"
  12. android:layout_gravity="bottom">
  13. </FrameLayout>
  14. </com.android.systemui.qs.PagedTileLayout>

public class PagedTileLayout extends ViewPager implements QSTileLayout

PagedTileLayout是一个ViewPager,我们熟悉ViewPager的用法,那么我们也就不难推断出PagedTileLayout的一些特性。比如滑动翻页。

继续之前mTileLayout.addTile(r)的代码研究

PagedTileLayout.addTile

  1. public void addTile(TileRecord tile) {
  2. mTiles.add(tile);
  3. postDistributeTiles();
  4. }

 private final ArrayList<TileRecord> mTiles = new ArrayList<>();是一个list,就是把数据保存起来

  1. private void postDistributeTiles() {
  2. removeCallbacks(mDistribute);
  3. post(mDistribute);
  4. }
  1. private final Runnable mDistribute = new Runnable() {
  2. @Override
  3. public void run() {
  4. distributeTiles();
  5. }
  6. };
  1. private void distributeTiles() {
  2. if (DEBUG) Log.d(TAG, "Distributing tiles");
  3. final int NP = mPages.size();
  4. for (int i = 0; i < NP; i++) {
  5. mPages.get(i).removeAllViews();
  6. }
  7. int index = 0;
  8. final int NT = mTiles.size();
  9. for (int i = 0; i < NT; i++) {
  10. TileRecord tile = mTiles.get(i);
  11. if (mPages.get(index).isFull()) {
  12. if (++index == mPages.size()) {
  13. if (DEBUG) Log.d(TAG, "Adding page for "
  14. + tile.tile.getClass().getSimpleName());
  15. mPages.add((TilePage) LayoutInflater.from(getContext())
  16. .inflate(R.layout.qs_paged_page, this, false));
  17. }
  18. }
  19. if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
  20. + index);
  21. mPages.get(index).addTile(tile);
  22. }
  23. if (mNumPages != index + 1) {
  24. mNumPages = index + 1;
  25. while (mPages.size() > mNumPages) {
  26. mPages.remove(mPages.size() - 1);
  27. }
  28. if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
  29. mPageIndicator.setNumPages(mNumPages);
  30. setAdapter(mAdapter);
  31. mAdapter.notifyDataSetChanged();
  32. setCurrentItem(0, false);
  33. }
  34. }

上面的代码中有一个比较关键的 mPages.get(index).addTile(tile);,

mPages是 private final ArrayList<TilePage> mPages = new ArrayList<>();

由于index=0.这也就导致只有第一页才会加载所有的快捷图标,实际我们使用也是这样的。

那我们来看看TilePage是什么

  1. public static class TilePage extends TileLayout {
  2. private int mMaxRows = 3;
  3. public TilePage(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. updateResources();
  6. }
  7. @Override
  8. public boolean updateResources() {
  9. final int rows = getRows();
  10. boolean changed = rows != mMaxRows;
  11. if (changed) {
  12. mMaxRows = rows;
  13. requestLayout();
  14. }
  15. return super.updateResources() || changed;
  16. }
  17. private int getRows() {
  18. //快捷面板显示的行数
  19. return Math.max(1, getResources().getInteger(R.integer.quick_settings_num_rows));
  20. }
  21. public void setMaxRows(int maxRows) {
  22. mMaxRows = maxRows;
  23. }
  24. public boolean isFull() {
  25. return mRecords.size() >= mColumns * mMaxRows;
  26. }
  27. }

继承了TileLayout,并且对于滑动的页数是由getRows()来决定的

TileLayout 我们看三个函数,因为这三个函数跟快捷按键的图标大小位置有关。

  1.updateResources  会初始化一些值,比如高度和边距等。

   2.onMeasure 主要确定TileLayout的宽度和高度,也就是快捷面板的宽高,并且确定子View的宽高

  3.onLayout确定子View的布局排列,是一行排三个还是一行排四个,都是在onLayout里面来实现。

  1. 捷面板里面的每个单元格图标的显示大小以及间隔
  2. public boolean updateResources() {
  3. final Resources res = mContext.getResources();
  4. final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
  5. mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
  6. mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
  7. mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
  8. mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
  9. mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
  10. if (mColumns != columns) {
  11. mColumns = columns;
  12. requestLayout();
  13. return true;
  14. }
  15. return false;
  16. }
  17. @Override
  18. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  19. final int numTiles = mRecords.size();
  20. final int width = MeasureSpec.getSize(widthMeasureSpec)
  21. - getPaddingStart() - getPaddingEnd();
  22. final int numRows = (numTiles + mColumns - 1) / mColumns;//每行的数量
  23. mCellWidth = (width - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns;//每个图标的宽度
  24. // Measure each QS tile.
  25. View previousView = this;
  26. for (TileRecord record : mRecords) {
  27. if (record.tileView.getVisibility() == GONE) continue;
  28. record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));//测量子View的宽度和高度
  29. previousView = record.tileView.updateAccessibilityOrder(previousView);
  30. }
  31. // Only include the top margin in our measurement if we have more than 1 row to show.
  32. // Otherwise, don't add the extra margin buffer at top.
  33. //最后计算出高度大小,
  34. int height = (mCellHeight + mCellMarginVertical) * numRows +
  35. (numRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0);
  36. if (height < 0) height = 0;
  37. setMeasuredDimension(width, height);
  38. }
  39. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  40. final int w = getWidth();
  41. final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
  42. int row = 0;
  43. int column = 0;
  44. // Layout each QS tile.
  45. //最后决定子View的位置在哪里
  46. for (int i = 0; i < mRecords.size(); i++, column++) {
  47. // If we reached the last column available to layout a tile, wrap back to the next row.
  48. if (column == mColumns) {
  49. column = 0;
  50. row++;
  51. }
  52. final TileRecord record = mRecords.get(i);
  53. final int top = getRowTop(row);
  54. final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
  55. final int right = left + mCellWidth;
  56. record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
  57. }
  58. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/111913
推荐阅读
相关标签
  

闽ICP备14008679号