SystemUI 7.0学习笔记三-QSPanel

来源:互联网 发布:iel数据库网址 编辑:程序博客网 时间:2024/05/17 01:33

QSPanel加载流程

首先通过一张图了解一下,如图红色区域就是QSPanel:包含亮度条和Quick Settings;本节的重点是后者。
这里写图片描述

QSPanel是StatusBar的一部分,自然是随着StatusBar的加载而加载,SystemUI 7.0学习笔记二-状态栏和导航栏介绍了状态栏的加载实在PhoneStatusBar的makeStatusBarView()中完成的;其实,这个方法完成了很多systemui重要组件的加载工作。再来回顾一下代码。
PhoneStatusBar.makeStatusBarView():

protected PhoneStatusBarView makeStatusBarView() {    final Context context = mContext;    inflateStatusBarWindow(context);    ......    // 这里完成了许多systemui关键组件的view创建    ......    // Set up the quick settings tile panel    AutoReinflateContainer container = (AutoReinflateContainer) mStatusBarWindow.findViewById(                R.id.qs_auto_reinflate_container);        if (container != null) {            final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,                    mBluetoothController, mLocationController, mRotationLockController,                    mNetworkController, mZenModeController, mHotspotController,                    mCastController, mFlashlightController,                    mUserSwitcherController, mUserInfoController, mKeyguardMonitor,                    mSecurityController, mBatteryController, mIconController,                    mNextAlarmController);            mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);            container.addInflateListener(new InflateListener() {                @Override                public void onInflated(View v) {                    QSContainer qsContainer = (QSContainer) v.findViewById(                            R.id.quick_settings_container);                    qsContainer.setHost(qsh);                    mQSPanel = qsContainer.getQsPanel();                    mQSPanel.setBrightnessMirror(mBrightnessMirrorController);                    mKeyguardStatusBar.setQSPanel(mQSPanel);                    mHeader = qsContainer.getHeader();                    initSignalCluster(mHeader);                    mHeader.setActivityStarter(PhoneStatusBar.this);                }            });        }        ......}

这里发现了mQSPanel,它是在QSContainer的onFinishInflate()中inflate的,接着SystemUIFactory通过createQSTileHost()调用new QSTileHost(…)创建了QSTileHost对象。
QSTileHost的继承关系及构造方法:

public class QSTileHost implements QSTile.Host, Tunable {    public QSTileHost(Context context, PhoneStatusBar statusBar, ...) {        ......        TunerService.get(mContext).addTunable(this, TILES_SETTING);    }}

注意:这里的TunerService是在SystemUI学习总结一-SystemUI的启动中和SystemBars等服务一起在SystemUIApplication开启的。

看到SERVICES 就想起来了吧!

    private final Class<?>[] SERVICES = new Class[] {            com.android.systemui.tuner.TunerService.class,            com.android.systemui.keyguard.KeyguardViewMediator.class,            com.android.systemui.recents.Recents.class,            com.android.systemui.volume.VolumeUI.class,            Divider.class,            com.android.systemui.statusbar.SystemBars.class,            com.android.systemui.usb.StorageNotification.class,            com.android.systemui.power.PowerUI.class,            com.android.systemui.media.RingtonePlayer.class,            com.android.systemui.keyboard.KeyboardUI.class,            com.android.systemui.tv.pip.PipUI.class,            com.android.systemui.shortcut.ShortcutKeyDispatcher.class,            com.android.systemui.VendorServices.class    };

好,言归正传。
继续看TunerService.addTunable(),

    public void addTunable(Tunable tunable, String... keys) {        for (String key : keys) {            addTunable(tunable, key);        }    }    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);        // 回调到QSTileHost        tunable.onTuningChanged(key, value);    }

上面代码中的value值为:
“airplane,wifi,cell,flashlight,bt,hotspot,location,rotation,battery,audioprofile,screenshots”这样的字符串,用于后面生成具体的QSTile。

QSTileHost.onTuningChanged():

    @Override    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); // 返回分割好的QSTile名字list        int currentUser = ActivityManager.getCurrentUser();        if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) 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) {            QSTile<?> tile = mTiles.get(tileSpec);            if (tile != null && (!(tile instanceof CustomTile)                    || ((CustomTile) tile).getUser() == currentUser)) {                if (DEBUG) Log.d(TAG, "Adding " + tile);                tile.removeCallbacks();                if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {                    tile.userSwitch(currentUser);                }                newTiles.put(tileSpec, tile);            } else {                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);                try {                    tile = createTile(tileSpec); // 根据名字创建具体的QSTile                    if (tile != null && tile.isAvailable()) {                        tile.setTileSpec(tileSpec);                        newTiles.put(tileSpec, tile);                    }                } 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();        }    }

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为空,就会从SystemUI的资源文件中去读取默认的tiles字符串。其实,SystemUI第一次的时候确实是从自己的资源文件中读取的quick_settings_tiles_default,只有编辑过tiles后才会将tiles字符串写入settings_secure.xml。

接着看createTile():

    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);        else if (tileSpec.equals("nfc")) return new NfcTile(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;        }    }

到这里,具体的tiles都创建好了,可是还是没看到怎么加到QSPanel的。让我们回到本文开始的地方
PhoneStatusBar.makeStatusBarView():

    qsContainer.setHost(qsh);

QSTileHost的构造函数中完成了具体的tiles,将它的对象set进来肯定有用,相信这里能够找到答案。

QSContainer.setHost(qsh):

    public void setHost(QSTileHost qsh) {        mQSPanel.setHost(qsh, mQSCustomizer);        //mHeader(StatusBarHeaderView) QSPanel加入到了StatusBarHeaderView中        mHeader.setQSPanel(mQSPanel);         mQSDetail.setHost(qsh);        mQSAnimator.setHost(qsh);    }

接着看QSPanel.setHost(qsh):

    public void setHost(QSTileHost host, QSCustomizer customizer) {        mHost = host;        mHost.addCallback(this);        setTiles(mHost.getTiles());        mFooter.setHost(host);        mCustomizePanel = customizer;        if (mCustomizePanel != null) {            mCustomizePanel.setHost(mHost);        }        mBrightnessController.setBackgroundLooper(host.getLooper());    }

好像发现了什么,对,就是mHost.getTiles()。
接着看QSPanel.setTiles(tiles):

    public void setTiles(Collection<QSTile<?>> tiles) {        setTiles(tiles, false);    }    public void setTiles(Collection<QSTile<?>> tiles, boolean collapsedView) {        for (TileRecord record : mRecords) {            mTileLayout.removeTile(record);            record.tile.removeCallback(record.callback);        }        mRecords.clear();        for (QSTile<?> tile : tiles) {            // 在这里,具体的每个QStitle在这里被addTile进了QSPanel            addTile(tile, collapsedView);        }    }

QSPanel.addTile(tile):

    protected void 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;        final View.OnClickListener click = new View.OnClickListener() {            @Override            public void onClick(View v) {                onTileClick(r.tile);            }        };        final View.OnLongClickListener longClick = new View.OnLongClickListener() {            @Override            public boolean onLongClick(View v) {                r.tile.longClick();                return true;            }        };        r.tileView.init(click, longClick);        r.tile.refreshState();        mRecords.add(r);        if (mTileLayout != null) {            mTileLayout.addTile(r);        }    }

上面addTile()代码中创建了一个Callback,实际运行中Title变化刷新,点击事件等许多操作都将与这个Callback挂钩。

mTileLayout(PagedTileLayout)是QSPanel的child,

    protected void setupTileLayout() {        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(                R.layout.qs_paged_tile_layout, this, false);        mTileLayout.setListening(mListening);        addView((View) mTileLayout);    }

PagedTileLayout.addTile(r)调用流程:

    @Override    public void addTile(TileRecord tile) {        mTiles.add(tile);        postDistributeTiles();    }    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(mContext)                            .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); // add 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);            mDecorGroup.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE);            setAdapter(mAdapter);            mAdapter.notifyDataSetChanged();            setCurrentItem(0, false);        }    }

mPages的每一页都是TilePage,TilePage的父类是TileLayout:

private final ArrayList<TilePage> mPages = new ArrayList<TilePage>();
public static class TilePage extends TileLayout {        ......}

接着看TileLayout.addTile():

    public void addTile(TileRecord tile) {        mRecords.add(tile);        tile.tile.setListening(this, mListening);        addView(tile.tileView); // 加载tile视图    }

至此,本文就告一段落了。

参考文章

  1. http://blog.csdn.net/qq_27215521/article/details/62888832
  2. http://blog.csdn.net/Otaku_627/article/details/53573040
  3. http://www.jianshu.com/p/502ac67d78b7
原创粉丝点击