android6.0 SystemUI之快捷设置区域QSPanel及点击事件流程分析

note:查看android源码可以访问https://github.com/android

SystemUI下拉之后的那些快捷设置菜单选项也是属于SystemUI的一种;它的加载也是随着PhoneStatusBar的加载而加载;

首先从布局方面入手:

快捷设置区域的布局是由PhoneStatusBar.java的makeStatusBarView()统一加载;

mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
                R.layout.super_status_bar, null);
加载frameworks/base/packages/SystemUI/res/layout/super_status_bar.xml布局

<com.android.systemui.statusbar.phone.PanelHolder
        android:id="@+id/panel_holder"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/transparent" >
        <include layout="@layout/status_bar_expanded"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />
    </com.android.systemui.statusbar.phone.PanelHolder>
而这个布局回去include另外一个布局status_bar_expanded.xml布局,frameworks/base/packages/SystemUI/res/layout/status_bar_expanded.xml;

<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">

        <com.android.systemui.statusbar.phone.ObservableScrollView
            android:id="@+id/scroll_view"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_gravity="@integer/notification_panel_layout_gravity"
            android:scrollbars="none"
            android:overScrollMode="never"
            android:fillViewport="true">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <include
                    layout="@layout/qs_panel"
                    android:layout_marginTop="@dimen/status_bar_header_height_expanded"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="@dimen/notification_side_padding"
                    android:layout_marginRight="@dimen/notification_side_padding"/>

                <!-- A view to reserve space for the collapsed stack -->
                <!-- Layout height: notification_min_height + bottom_stack_peek_amount -->
                <View
                    android:id="@+id/reserve_notification_space"
                    android:layout_height="@dimen/min_stack_height"
                    android:layout_width="match_parent"
                    android:layout_marginTop="@dimen/notifications_top_padding" />

                <View
                    android:layout_height="@dimen/notification_side_padding"
                    android:layout_width="match_parent" />
            </LinearLayout>
        </com.android.systemui.statusbar.phone.ObservableScrollView>

        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
            android:id="@+id/notification_stack_scroller"
            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"
            android:importantForAccessibility="no" />

        <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" />

    </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
同样的在这个布局文件中又会去include一个qs_panel.xml布局,frameworks/base/packages/SystemUI/res/layout/qs_panel.xml;

<com.android.systemui.qs.QSContainer
        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:background="@drawable/qs_background_primary"
        android:paddingTop="8dp"
        android:paddingBottom="8dp"
        android:elevation="2dp">

    <com.android.systemui.qs.QSPanel
            android:id="@+id/quick_settings_panel"
            android:background="#0000"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
</com.android.systemui.qs.QSContainer>

见名知意,这个id为quick_settings_panel即为我们所找的那个SystemUI上的快捷设置区域控件的ID;


代码控制方面:

同样的也是由PhoneStatusBar.java的makeStatusBarView()方法开始的。

        // Set up the quick settings tile panel
        mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
        if (mQSPanel != null) {
            final QSTileHost qsh = new QSTileHost(mContext, this,
                    mBluetoothController, mLocationController, mRotationLockController,
                    mNetworkController, mZenModeController, mHotspotController,
                    mCastController, mFlashlightController,
                    mUserSwitcherController, mKeyguardMonitor,
                    mSecurityController);
            mQSPanel.setHost(qsh);
            mQSPanel.setTiles(qsh.getTiles());
            mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
            mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
            mHeader.setQSPanel(mQSPanel);
            qsh.setCallback(new QSTileHost.Callback() {
                @Override
                public void onTilesChanged() {
                    mQSPanel.setTiles(qsh.getTiles());
                }
            });

一步步分析,首先先实例化一个QSPanel对象,然后再去创建QSTileHost对象,其传入的参数即为各种快捷设置控制器,如蓝牙、屏幕旋转、定位、闪光灯等等;

分析QSTileHost.java的构造方法:

public QSTileHost(Context context, PhoneStatusBar statusBar,
            BluetoothController bluetooth, LocationController location,
            RotationLockController rotation, NetworkController network,
            ZenModeController zen, HotspotController hotspot,
            CastController cast, FlashlightController flashlight,
            UserSwitcherController userSwitcher, UserInfoController userInfo,
            KeyguardMonitor keyguard, SecurityController security,
            BatteryController battery, StatusBarIconController iconController,
            NextAlarmController nextAlarmController) {
        mContext = context;
        mStatusBar = statusBar;
        mBluetooth = bluetooth;
        mLocation = location;
        mRotation = rotation;
        mNetwork = network;
        mZen = zen;
        mHotspot = hotspot;
        mCast = cast;
        mFlashlight = flashlight;
        mUserSwitcherController = userSwitcher;
        mUserInfoController = userInfo;
        mKeyguard = keyguard;
        mSecurity = security;
        mBattery = battery;
        mIconController = iconController;
        mNextAlarmController = nextAlarmController;
        mProfileController = new ManagedProfileController(this);

        final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
                Process.THREAD_PRIORITY_BACKGROUND);
        ht.start();
        mLooper = ht.getLooper();

        mServices = new TileServices(this, mLooper);

        TunerService.get(mContext).addTunable(this, TILES_SETTING);
    }
1、实例化各个控制器,创建一个子线程handler获取Looper对象
2、使用TunerService去Settings中查询key为TILES_SETTING的值,即查询快捷设置菜单项,查询到的结果通过onTuningChanged()方法回调返回;

frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java

private void addTunable(Tunable tunable, String key) {
        if (!mTunableLookup.containsKey(key)) {
            mTunableLookup.put(key, new ArraySet<Tunable>());
        }
        mTunableLookup.get(key).add(tunable);
        Uri uri = Settings.Secure.getUriFor(key);
        if (!mListeningUris.containsKey(uri)) {
            mListeningUris.put(uri, key);
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
        // Send the first state.
        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
        tunable.onTuningChanged(key, value);
    }

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java的onTuningChanged()方法

@Override
    public void onTuningChanged(String key, String newValue) {
        if (!TILES_SETTING.equals(key)) {
            return;
        }
        if (DEBUG) Log.d(TAG, "Recreating tiles");
        final List<String> tileSpecs = loadTileSpecs(newValue);
        if (tileSpecs.equals(mTileSpecs)) return;
        for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
            if (!tileSpecs.contains(tile.getKey())) {
                if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
                tile.getValue().destroy();
            }
        }
        final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
        for (String tileSpec : tileSpecs) {
            if (mTiles.containsKey(tileSpec)) {
                newTiles.put(tileSpec, mTiles.get(tileSpec));
            } else {
                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                try {
                    newTiles.put(tileSpec, createTile(tileSpec));
                } catch (Throwable t) {
                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
                }
            }
        }
        mTileSpecs.clear();
        mTileSpecs.addAll(tileSpecs);
        mTiles.clear();
        mTiles.putAll(newTiles);
        if (mCallback != null) {
            mCallback.onTilesChanged();
        }
    }

在通过调用loadTileSpecs()方法对查询的结果进行判断处理;

protected List<String> loadTileSpecs(Context context, String tileList) {
        final Resources res = context.getResources();
        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
        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;
    }
如果返回的结果tileList不为null,使用","来拆分结果,对得到的每个tile进行判断,如果不为"default",即保存此tile;
如果返回的结果tileList为null,则tileList赋值为"default",并读取config.xml中的quick_settings_tiles_default字串,拆分保存;
返回所符合要求显示的快捷设置tile集合;

然后继续在QSTileHost.java的onTuningChanged()方法中调用createTile()方法来创建每一个快捷设置的tile对象;

public QSTile<?> createTile(String tileSpec) {
        if (tileSpec.equals("wifi")) return new WifiTile(this);
        else if (tileSpec.equals("bt")) return new BluetoothTile(this);
        else if (tileSpec.equals("cell")) return new CellularTile(this);
        else if (tileSpec.equals("dnd")) return new DndTile(this);
        else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
        else if (tileSpec.equals("work")) return new WorkModeTile(this);
        else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
        else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
        else if (tileSpec.equals("location")) return new LocationTile(this);
        else if (tileSpec.equals("cast")) return new CastTile(this);
        else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
        else if (tileSpec.equals("user")) return new UserTile(this);
        else if (tileSpec.equals("battery")) return new BatteryTile(this);
        else if (tileSpec.equals("saver")) return new DataSaverTile(this);
        else if (tileSpec.equals("night")) return new NightDisplayTile(this);
        // Intent tiles.
        else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
        else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
        else {
            Log.w(TAG, "Bad tile spec: " + tileSpec);
            return null;
        }
    }
并将其保存在成员变量的mTiles集合中,最后回调onTilesChanged()方法,通知PhoneStatusBar.java对快捷设置选项显示更新;

if (mCallback != null) {
            mCallback.onTilesChanged();
        }

至此QSTileHost.java的构造方法分析完成,然后再回到调用处PhoneStatusBar.java的makeStatusBarView()方法继续分析:

mQSPanel.setHost(qsh);
mQSPanel.setTiles(qsh.getTiles());
设置QSPanel.setHost()、设置QSPanel.setTiles();而其中的其中setTiles()方法会先remove掉所有的TileRecord记录并移除所有的tileView;

public void setTiles(Collection<QSTile<?>> tiles) {
        for (TileRecord record : mRecords) {
            removeView(record.tileView);
        }
        mRecords.clear();
        for (QSTile<?> tile : tiles) {
            addTile(tile);
        }
        if (isShowingDetail()) {
            mDetail.bringToFront();
        }
    }

然后在重新调用addTile()创建TileRecord对象并赋值绑定相应的回调和点击事件(点击、双击、长按)接口,再将其保存到ArrayList<TileRecord> mRecords集合中,然后再去addView();

private void addTile(final QSTile<?> tile) {
        final TileRecord r = new TileRecord();
        r.tile = tile;
        r.tileView = tile.createTileView(mContext);
        r.tileView.setVisibility(View.GONE);
        final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                int visibility = state.visible ? VISIBLE : GONE;
                if (state.visible && !mGridContentVisible) {

                    // We don't want to show it if the content is hidden,
                    // then we just set it to invisible, to ensure that it gets visible again
                    visibility = INVISIBLE;
                }
                setTileVisibility(r.tileView, visibility);
                r.tileView.onStateChanged(state);
            }
            @Override
            public void onShowDetail(boolean show) {
                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) {
                announceForAccessibility(announcement);
            }
        };
        r.tile.setCallback(callback);
        final View.OnClickListener click = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                r.tile.click();
            }
        };
        final View.OnClickListener clickSecondary = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                r.tile.secondaryClick();
            }
        };
        final View.OnLongClickListener longClick = new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                r.tile.longClick();
                return true;
            }
        };
        r.tileView.init(click, clickSecondary, longClick);
        r.tile.setListening(mListening);
        callback.onStateChanged(r.tile.getState());
        r.tile.refreshState();
        mRecords.add(r);

        addView(r.tileView);
    }
再回到调用处PhoneStatusBar.java的makeStatusBarView()方法继续分析:
qsh.setCallback(new QSTileHost.Callback() {
                @Override
                public void onTilesChanged() {
                    mQSPanel.setTiles(qsh.getTiles());
                }
            });

为QSTileHost的对象设置onTilesChanged()回调监听;

至此完成快捷区域加载显示的大致流程分析。



接下来,分析下点击某一个快捷设置图标执行的逻辑流程,以BluetoothTile为例,即点击蓝牙快捷设置后的流程分析如下:

1、由于在加载显示快捷区域图标的时候已经为每一个BluetoothTile设置了点击事件等回调监听,具体代码逻辑参看QSPanel.java的addTile()方法。点击后,由QSPanel.java的addTile()方法中的View.OnClickListener click先行处理,调用r.tile.click();

final View.OnClickListener click = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                r.tile.click();
            }
        };
即调用QSTile.java的click()方法,而在这个方法内会去通过发送msg为H.CLICK的消息到handler中
public void click() {
        mHandler.sendEmptyMessage(H.CLICK);
    }

在此handler中去调用抽象方法handleClick()处理此次点击事件;

if (msg.what == CLICK) {
                    name = "handleClick";
                    mAnnounceNextStateChange = true;
                    handleClick();
                }
由于handleClick()是抽象方法,其实现是在QSTile.java的子类中,由于点击的是蓝牙设置,可以在BluetoothTile.java中找到实现逻辑;

在此方法中,调用BluetoothController去控制设置蓝牙状态。

protected void handleClick() {
        final boolean isEnabled = (Boolean)mState.value;
        mController.setBluetoothEnabled(!isEnabled);
    }
而这个mController是从QSTileHost.java中获取的。由于BluetoothController.java是一个接口,其具体实现是在BluetoothControllerImpl.java中,此方法会去通过调用蓝牙标准接口来实现蓝牙的开启/关闭状态设置;

@Override
    public void setBluetoothEnabled(boolean enabled) {
        if (mLocalBluetoothManager != null) {
            mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
        }
    }
此时,当蓝牙状态设置成功后,则会回调BluetoothControllerImpl.java中的onBluetoothStateChanged()方法,这个方法会发送一个msg为H.MSG_STATE_CHANGED的消息到handler中

@Override
    public void onBluetoothStateChanged(int bluetoothState) {
        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
                || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
        mState = bluetoothState;
        mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
    }
接下来会在此handler中调用fireStateChange()方法;

case MSG_STATE_CHANGED:
    fireStateChange();
    break;

fireStateChange()这个方法中又会去回调onBluetoothStateChange()方法;

        private void fireStateChange() {
            for (BluetoothController.Callback cb : mCallbacks) {
                fireStateChange(cb);
            }
        }

        private void fireStateChange(BluetoothController.Callback cb) {
            cb.onBluetoothStateChange(mEnabled);
        }
这个回调onBluetoothStateChange()方法的实现是在BluetoothTile.java中;

private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
        @Override
        public void onBluetoothStateChange(boolean enabled) {
            refreshState();
        }

        @Override
        public void onBluetoothDevicesChanged() {
            mUiHandler.post(new Runnable() {
                @Override
                public void run() {
                    mDetailAdapter.updateItems();
                }
            });
            refreshState();
        }
    };
这个方法又是在调用refreshState(方法,其实现则是在父类QSTile.java的refreshState()方法;refreshState()方法通过发送msg为H.REFRESH_STATE通知handler;

public final void refreshState() {
        refreshState(null);
    }

    protected final void refreshState(Object arg) {
        mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
    }

这个handler中又会去调用handleRefreshState()方法

if (msg.what == REFRESH_STATE) {
                    name = "handleRefreshState";
                    handleRefreshState(msg.obj);
                } 

此方法再去调用handleUpdateState()方法,

protected void handleRefreshState(Object arg) {
        handleUpdateState(mTmpState, arg);
        final boolean changed = mTmpState.copyTo(mState);
        if (changed) {
            handleStateChanged();
        }
    }
由于handleUpdateState()是一个抽象方法,最终实现是它的子类,也就是说还是调回了BluetoothTile.java中,在这个方法中去实现设置图标的icon和描述信息contentDescription;

@Override
    protected void handleUpdateState(BooleanState state, Object arg) {
        final boolean enabled = mController.isBluetoothEnabled();
        final boolean connected = mController.isBluetoothConnected();
        final boolean connecting = mController.isBluetoothConnecting();
        state.value = enabled;
        state.autoMirrorDrawable = false;
        state.minimalContentDescription =
                mContext.getString(R.string.accessibility_quick_settings_bluetooth);
        if (enabled) {
            state.label = null;
            if (connected) {
                state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
                state.label = mController.getLastDeviceName();
                state.contentDescription = mContext.getString(
                        R.string.accessibility_bluetooth_name, state.label);
                state.minimalContentDescription = state.minimalContentDescription + ","
                        + state.contentDescription;
            } else if (connecting) {
                state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connecting);
                state.contentDescription = mContext.getString(
                        R.string.accessibility_quick_settings_bluetooth_connecting);
                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
                state.minimalContentDescription = state.minimalContentDescription + ","
                        + state.contentDescription;
            } else {
                state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
                state.contentDescription = mContext.getString(
                        R.string.accessibility_quick_settings_bluetooth_on) + ","
                        + mContext.getString(R.string.accessibility_not_connected);
                state.minimalContentDescription = state.minimalContentDescription + ","
                        + mContext.getString(R.string.accessibility_not_connected);
            }
            if (TextUtils.isEmpty(state.label)) {
                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
            }
        } else {
            state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);
            state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
            state.contentDescription = mContext.getString(
                    R.string.accessibility_quick_settings_bluetooth_off);
        }

        CharSequence bluetoothName = state.label;
        if (connected) {
            bluetoothName = state.dualLabelContentDescription = mContext.getString(
                    R.string.accessibility_bluetooth_name, state.label);
        }
        state.dualLabelContentDescription = bluetoothName;
        state.contentDescription = state.contentDescription + "," + mContext.getString(
                R.string.accessibility_quick_settings_open_settings, getTileLabel());
        state.expandedAccessibilityClassName = Button.class.getName();
        state.minimalAccessibilityClassName = Switch.class.getName();
    }
回到QSTile.java的handleUpdateState()方法,继续调用handleStateChanged()方法,这个方法主要是用来调用回调方法onStateChanged()方法;

 private void handleStateChanged() {
        boolean delayAnnouncement = shouldAnnouncementBeDelayed();
        if (mCallbacks.size() != 0) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onStateChanged(mState);
            }
            if (mAnnounceNextStateChange && !delayAnnouncement) {
                String announcement = composeChangeAnnouncement();
                if (announcement != null) {
                    mCallbacks.get(0).onAnnouncementRequested(announcement);
                }
            }
        }
        mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
    }
onStateChanged()最终的实现是在QSPanel.java下的addTile()方法内

final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                drawTile(r, state);
            }
在往下调用drawTile()方法,在drawTile()方法中再去调QSTileView.java的onStateChanged()方法,发送msg为H.STATE_CHANGED的消息到handler中;
public void onStateChanged(QSTile.State state) {
        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
    }

在这个handler中再去调用方法handleStateChanged();

if (msg.what == STATE_CHANGED) {
                handleStateChanged((State) msg.obj);
            }

最终在handleStateChanged()中实现界面显示,完成整个流程。

protected void handleStateChanged(QSTile.State state) {
        if (mIcon instanceof ImageView) {
            setIcon((ImageView) mIcon, state);
        }
        if (mDual) {
            mDualLabel.setText(state.label);
            mDualLabel.setContentDescription(state.dualLabelContentDescription);
            mTopBackgroundView.setContentDescription(state.contentDescription);
        } else {
            mLabel.setText(state.label);
            setContentDescription(state.contentDescription);
        }
    }


总结:
1、页面View的加载起始实在PhoneStatusBar.java的makeStatusBarView();
2、菜单项首先会去系统中查询Settings.Secure.TILES_SETTING,存在则拆分生成tile,否则的话就去读取config.xml中的quick_settings_tiles_default值,再拆分生成tile;
3、每一个QSTile的子类如BluetoothTile.java,由它自己来最终实现图标view的更新和状态控制,由QSTileView.java来最终显示到界面上



























猜你喜欢

转载自blog.csdn.net/Otaku_627/article/details/53573040