前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 9.0 SystemUI 下拉状态栏快捷开关

Android 9.0 SystemUI 下拉状态栏快捷开关

作者头像
吴小龙同學
发布2020-10-30 10:53:03
4.6K0
发布2020-10-30 10:53:03
举报
文章被收录于专栏:吴小龙同學吴小龙同學

SystemUI 下拉状态栏快捷开关是 QSPanel,qs_panel.xml,@+id/quick_settings_panel,本篇文章就来看看这些快捷开关是如何呈现的以及如何新增一个快捷开关?基于 AOSP 9.0 分析。

SystemUI 下拉状态栏快捷开关

QSPanel 创建是从 StatusBar#makeStatusBarView 开始的。

StatusBar#makeStatusBarView

代码语言:javascript
复制
protected void makeStatusBarView() {
    //省略其他代码
    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);
        }
    });
    //省略其他代码
}

先看 SystemUIFactory#createQSTileHost。

SystemUIFactory#createQSTileHost

代码语言:javascript
复制
public QSTileHost createQSTileHost(Context context, StatusBar statusBar,
        StatusBarIconController iconController) {
    return new QSTileHost(context, statusBar, iconController);
}

这里进行 QSTileHost 初始化。

QSTileHost#构造函数

代码语言:javascript
复制
public QSTileHost(Context context, StatusBar statusBar,
        StatusBarIconController iconController) {
    //省略其他代码
    Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);
    //省略其他代码
}

这里进行了 TunerService 注册,在 TunerServiceImpl#addTunable 重写。

TunerServiceImpl#addTunable

代码语言:javascript
复制
@Override
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);
    if (LeakDetector.ENABLED) {
        mTunables.add(tunable);
        Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
    }
    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);
}

tunable.onTuningChanged 回调 QSTileHost#onTuningChanged。

QSTileHost#onTuningChanged

代码语言:javascript
复制
@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);
    }
    //调用 QSTileHost#loadTileSpecs,获得 config 里字符串信息
    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
                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();
    }
}

看下 QSTileHost#loadTileSpecs,是获得 config 里字符串信息。

QSTileHost#loadTileSpecs

代码语言:javascript
复制
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;
}

其中 quick_settings_tiles_default 值在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里:

代码语言:javascript
复制
<string name="quick_settings_tiles_default" translatable="false">
    wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
</string>

这里就是我们所看到的快捷开关的文本描述。

再看 QSTileHost#onTuningChanged 中的调用 QSTileHost#createTile 方法。

QSTileHost#createTile

代码语言:javascript
复制
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;
        }
    }
    return null;
}

调用 QSFactory#createTile,由 QSFactoryImpl#createTile 实现了。

QSFactoryImpl#createTile

代码语言:javascript
复制
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) {
    // 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);
    }
    // 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;
}

看到这里通过对应的字符串分别实例化 Tile。

以上涉及资源文件加载及对应实例化,接下来看看代码如何加载的,看 QSPanel#onAttachedToWindow 方法。

QSPanel#onAttachedToWindow

代码语言:javascript
复制
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    final TunerService tunerService = Dependency.get(TunerService.class);
    tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
    if (mHost != null) {
        setTiles(mHost.getTiles());
    }
    if (mBrightnessMirrorController != null) {
        mBrightnessMirrorController.addCallback(this);
    }
}
public void setTiles(Collection<QSTile> tiles) {
    setTiles(tiles, false);
}
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);
    //省略其他代码
    r.tileView.init(r.tile);
    r.tile.refreshState();
    mRecords.add(r);
    if (mTileLayout != null) {
        mTileLayout.addTile(r);
    }
    return r;
}

mTileLayout.addTile(r);由 PagedTileLayout#addTile 实现。

PagedTileLayout#addTile

PagedTileLayout 是 ViewPager,重点看 setAdapter,看数据源如何 add 的。

代码语言:javascript
复制
@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(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);
    }
}

至此,SystemUI 下拉状态栏快捷开关模块代码流程分析完毕。

新增一个快捷开关

0、国际惯例,先上效果图,新增一个Camera,随便用了蓝牙的图标:

1、首先在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里面添加截屏 Camera 的选项

代码语言:javascript
复制
<string name="quick_settings_tiles_default" translatable="false">
    wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,camera
</string>

2、在 AOSP/frameworks/base/packages/SystemUI/res/values/strings.xml 里面还要加一个字符串

1

<string name="quick_settings_camera_label">Camera</string>

3、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/ 目录下创建 CameraTile.java,实现 QSTileImpl:

代码语言:javascript
复制
package com.android.systemui.qs.tiles;
import android.content.Intent;
import android.provider.MediaStore;
import android.widget.Toast;
//手动添加
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
//手动添加
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class CameraTile extends QSTileImpl<QSTile.BooleanState> {
    public CameraTile(QSHost host) {
        super(host);
    }
    @Override
    public BooleanState newTileState() {
        return new BooleanState();
    }
    @Override
    protected void handleClick() {
        Toast.makeText(mContext,"Camera Click",Toast.LENGTH_LONG).show();
    }
    @Override
    protected void handleUpdateState(BooleanState state, Object arg) {
        state.label = mContext.getString(R.string.quick_settings_camera_label);
        //定义图标,随便用了蓝牙的图标
        state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
    }
    @Override
    public int getMetricsCategory() {
        return MetricsEvent.QS_CAMERA;
    }
    @Override
    public Intent getLongClickIntent() {
        return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    }
    @Override
    protected void handleSetListening(boolean listening) {
    }
    @Override
    public CharSequence getTileLabel() {
        return mContext.getString(R.string.quick_settings_camera_label);
    }
}

4、在 AOSP/frameworks/base/proto/src/metrics_constants.proto,增加常量:

1

QS_CAMERA = 1568;

5、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java,增加:

代码语言:javascript
复制
private QSTileImpl createTileInternal(String tileSpec) {
    // Stock tiles.
    switch (tileSpec) {
        case "wifi":
            return new WifiTile(mHost);
        // 省略部分代码
        case "nfc":
            return new NfcTile(mHost);
        case "camera":
            return new CameraTile(mHost);
    }
    // 省略部分代码
}

6、整编代码,运行模拟器,有效果,棒棒哒。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SystemUI 下拉状态栏快捷开关
    • StatusBar#makeStatusBarView
      • SystemUIFactory#createQSTileHost
        • QSTileHost#构造函数
          • TunerServiceImpl#addTunable
            • QSTileHost#onTuningChanged
              • QSTileHost#loadTileSpecs
                • QSTileHost#createTile
                  • QSFactoryImpl#createTile
                    • QSPanel#onAttachedToWindow
                      • PagedTileLayout#addTile
                      • 新增一个快捷开关
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档