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

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());
        }
    }

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

  • 14
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Android中,如果想要自定义下拉通知的颜色,可以通过修改SystemUI的相关设置来实现。 首先,为了修改SystemUI的颜色,需要获取相应的权限。我们可以在AndroidManifest.xml文件中添加如下代码: ```xml <uses-permission android:name="android.permission.STATUS_BAR"/> ``` 接下来,在我们的项目中创建一个名为values的文件夹,并在其中创建一个名为colors.xml的文件。在这个文件中,我们可以定义我们想要使用的颜色。例如,我们可以定义一个名为notification_background的颜色,用于设置下拉通知的背景颜色。代码如下: ```xml <resources> <color name="notification_background">#FF0000</color> </resources> ``` 然后,我们需要修改SystemUI的源代码,以更新背景颜色。具体来说,我们需要找到StatusBar类中的updateResources方法,并在该方法中添加以下代码: ```java Context context = mContext.createPackageContext("com.example.notificationtest", Context.CONTEXT_IGNORE_SECURITY); // 替换为自己的包名 int color = context.getResources().getColor(R.color.notification_background); mBackgroundView.setBackgroundColor(color); ``` 最后,我们需要重新编译并安装我们的应用程序。一旦安装完成,我们就可以看到下拉通知的背景颜色已经根据我们在colors.xml中定义的颜色进行了自定义。 以上是通过修改SystemUI的方式来自定义下拉通知的颜色。请注意,这种方式需要具备系统级权限,因此只适用于特定的Android设备。在实际开发中,请确保在使用这种方式之前了解并遵守相关的法规和政策,以避免违规行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值