Android5.1 快捷开关如何添加和刷新状态

本文详细解析了Android系统中快捷开关的添加与刷新机制,包括配置文件的作用、关键Java代码的实现过程,以及如何创建和刷新各个快捷开关的图标和文本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android 5.1的快捷开关的添加和刷新机制和Android6、Android7差不多是一样的。虽然现在Android7.0的快捷开关还没有认真的去研读,只是稍微的看了一下。Android6.0和Android7.0相比较于Android5.0的快捷开关增加了不少的代码。增加的代码主要是增加了下拉快捷面板的时候其中的一些动画,还有就是Android7.0中增加了快捷开关的自定义排序功能。但是万变不离其宗。下面切入到正题。

先看一下定制的快捷开关的UI样式。

快捷面板上面有很多的快捷开关,但是其中各个快捷开关的是如何运作的还不是很清楚。


好了言归正传。

添加快捷开关首先需要去配置文件config.xml文件中去添加快捷开关的名称。

config.xml文件的路径是:frameworks/base/packages/SystemUI/res/values/config.xml中。其关键代码如下:

<!-- The default tiles to display in QuickSettings -->
    <string name="quick_settings_tiles_default" translatable="false">
        wifi,bt,data,airplane,audioprofile,hotspot,dnd,powersave,rotation,location,flashlight,takescreenshot
    </string>

关键的java代码主要是在:frameworks/base/packages/SystemUI/com/android/systemui/qs 下面,包括QSPanel.java、

QSTile.java、QSTileView.java等。

在手机启动的时候,首先会去加载和创建和初始化PhoneStatusBar.java (frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/)中的控件和类。当然也包括快捷面板的

初始化操作。PhoneStatusBar中关于快捷面板的关键代码如下:

// Set up the quick settings tile panel
        mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);//找到QSPanel
        if (mQSPanel != null) {

	    //初始化 QSTileHost
            final QSTileHost qsh = new QSTileHost(mContext, this,
                    mBluetoothController, mLocationController, mRotationLockController,
                    mNetworkController, mZenModeController, mHotspotController,
                    mCastController, mFlashlightController,
                    mUserSwitcherController, mKeyguardMonitor,
                    mSecurityController,
                    /// M: add HotKnot in quicksetting
                    mHotKnotController,
                    /// M: add AudioProfile in quicksetting
                    mAudioProfileController
            );
            mQSPanel.setHost(qsh);

	    //通过QSTileHost获得的各个快捷开关(qsh.getTiles())加入到QSPanel中。
            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());
                }
            });
        }

通过跟踪上面的代码,我们可以看到 需要通过QSTileHost去获取快捷开关(qsh.getTiles())。那么,qsh.getTiles()

是如何去获取到各个快捷开关的呢?我们一起去看看吧。打开QSTileHost.java类。找到getTiles()方法。

 @Override
    public Collection<QSTile<?>> getTiles() {
        return mTiles.values();
    }
mTiles的类型:

LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();

找到mTiles是在哪赋值的,通过查找可以找到是在下面的这个类中。

@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() 方法和 createTile()方法。那我们看看loadTileSpecs方法的具体作用是什么。


//loadTileSpecs() 此方法主要是去之前配置好的config.xml文件中获取各个快捷开关的名称。然后拆分字符串。

 protected List<String> loadTileSpecs(String tileList) {
        final Resources res = mContext.getResources();
        //获取Config.xml中配置需要显示的快捷开关的名称。
        String defaultTileList = res.getString(R.string.quick_settings_tiles_default);

        // M: Add extra tiles @{
        defaultTileList += "," + res.getString(R.string.quick_settings_tiles_extra);//default
        // @}
        /// M: Customize the quick settings tile order for operator. @{
        IQuickSettingsPlugin quickSettingsPlugin = PluginFactory.getQuickSettingsPlugin(mContext);
        defaultTileList = quickSettingsPlugin.customizeQuickSettingsTileOrder(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;
    }

//createTile() 方法的作用是 创建各个快捷开关的对象。

 private QSTile<?> createTile(String tileSpec) {
        if (tileSpec.equals("wifi") && !WCN_DISABLED) return new WifiTile(this);
        else if (tileSpec.equals("bt") && !WCN_DISABLED) return new BluetoothTile(this);
        else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
        else if (tileSpec.equals("cell")) return new CellularTile(this);
        else if (tileSpec.equals("data")) return new DataConnectionTile(this);
        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
//        else if (tileSpec.equals("lte") && TelephonyManagerSprd.isDeviceSupportLte()) return new LteServiceTile(this);
//        else if (tileSpec.equals("lte") && isDeviceSupportLte()) return new LteServiceTile(this);
        else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
        else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
        else if (tileSpec.equals("location") && !WCN_DISABLED) 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("diabolo")) return new DiaboloTile(this);
        else if (tileSpec.equals("dnd")) return new DndTile(this);
        else if (tileSpec.equals("audioprofile") ) //&& SIMHelper.isMtkAudioProfilesSupport()
            return new AudioProfileTile(this);
//        else if (tileSpec.equals("interruption")) return new InterruptionsTile(this);
        else if (tileSpec.equals("powersave")) return new PowerSaveTile(this);
        else if (tileSpec.equals("takescreenshot")) return new TakeScreenShotTile(this);
        else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
        else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
    }

好了,经过上面的分析,终于知道配置文件的作用。知道使如何创建的了之后,但是还没有完。下面来看看他是如何

刷新和更新快捷开关的图标和文本的。

我们还是需要从上面的 PhoneStateBar.java 中的 代码中继续往下分析,要分析的代码如下:

//通过QSTileHost获得的各个快捷开关(qsh.getTiles())加入到QSPanel中。
            mQSPanel.setTiles(qsh.getTiles());
关键的是QSPanel.java类中的setTiles()方法。

public void setTiles(Collection<QSTile<?>> tiles) {
		for (TileRecord record : mRecords) {
			removeView(record.tileView);
		}
		mRecords.clear();
		int row = tiles.size() / mColumns + 1;
		if (mDeviderViewGroup == null) {
			mDeviderViewGroup = new View[row];
		} else {
			for (int i = 0; i < mDeviderViewGroup.length; i++) {
				removeView(mDeviderViewGroup[i]);
			}
			mDeviderViewGroup = new View[row];
		}
		for (int i = 0; i < row; i++) {
			addDividerLine(i);
		}
		for (QSTile<?> tile : tiles) {
			addTile(tile);
		}
		if (isShowingDetail()) {
			mDetail.bringToFront();
		}
	}
上面setTiles()方法中,需要注意的是addTile()方法。如果打开QSPanel.java 类,我们会知道QSPanel类

继承于ViewGroup的。下面我们在来看看addTile()方法的做了写什么。

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) {
                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);
    }
我们可以看到addTile()方法的最后面是调用的addView方法。说明是将各个快捷开关添加到QSPanel的ViewGroup当中。

成为QSPanel的子控件。

下面我们在来看看 子控件。这里以手电筒(FlashlightTile.java)为例来说明其实如何来刷新图标和文本的。



//FlashlightTile.java  

public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
        FlashlightController.FlashlightListener {
	......
	@Override
    	protected void handleClick() {
        	if (ActivityManager.isUserAMonkey()) {
            	return;
        	}
        	MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
        	boolean newState = !mState.value;
        	refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
        	mFlashlightController.setFlashlight(newState);
        	Settings.System.putInt(mContext.getContentResolver(), "open_flashlight_by_quicksetting_panel", newState ? 1 : 0); // 1 打开 0 关闭
    	}
	.......
}

FlashlightTile.java 中用来更新手电筒图标和文本的代码是refreshState() 去刷新。当调用refreshState的时候

会去调用QSTile.java类中的freshState(Object arg)方法。

 protected final void refreshState(Object arg) {
        mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
    }
protected final class H extends Handler {
        private static final int SET_CALLBACK = 1;
        private static final int CLICK = 2;
        private static final int SECONDARY_CLICK = 3;
        private static final int LONG_CLICK = 4;
        private static final int REFRESH_STATE = 5;
        private static final int SHOW_DETAIL = 6;
        private static final int USER_SWITCH = 7;
        private static final int TOGGLE_STATE_CHANGED = 8;
        private static final int SCAN_STATE_CHANGED = 9;
        private static final int DESTROY = 10;

        private H(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            String name = null;
            try {
                if (msg.what == SET_CALLBACK) {
                    name = "handleSetCallback";
                    handleSetCallback((QSTile.Callback)msg.obj);
                } else if (msg.what == CLICK) {
                    name = "handleClick";
                    mAnnounceNextStateChange = true;
                    handleClick();
                } else if (msg.what == SECONDARY_CLICK) {
                    name = "handleSecondaryClick";
                    handleSecondaryClick();
                } else if (msg.what == LONG_CLICK) {
                    name = "handleLongClick";
                    handleLongClick();
                } else if (msg.what == REFRESH_STATE) {
                    name = "handleRefreshState";
                    handleRefreshState(msg.obj);
                } else if (msg.what == SHOW_DETAIL) {
                    name = "handleShowDetail";
                    handleShowDetail(msg.arg1 != 0);
                } else if (msg.what == USER_SWITCH) {
                    name = "handleUserSwitch";
                    handleUserSwitch(msg.arg1);
                } else if (msg.what == TOGGLE_STATE_CHANGED) {
                    name = "handleToggleStateChanged";
                    handleToggleStateChanged(msg.arg1 != 0);
                } else if (msg.what == SCAN_STATE_CHANGED) {
                    name = "handleScanStateChanged";
                    handleScanStateChanged(msg.arg1 != 0);
                } else if (msg.what == DESTROY) {
                    name = "handleDestroy";
                    handleDestroy();
                } else {
                    throw new IllegalArgumentException("Unknown msg: " + msg.what);
                }
            } catch (Throwable t) {
                final String error = "Error in " + name;
                Log.w(TAG, error, t);
                mHost.warn(error, t);
            }
        }
    }
 protected void handleRefreshState(Object arg) {
        handleUpdateState(mTmpState, arg);
        final boolean changed = mTmpState.copyTo(mState);
        if (changed) {
            handleStateChanged();
        }
    }

跟踪代码,最终发现调用FlashlightTile.java中的refreState()方法,最后会调用QSTile.java的handleRefreshState

方法。handleRefreshState方法又会调用handleUpdateState和handleStateChanged() 两个方法。handleUpdateState

方法会去调用QSPanel的子控件FlashlightTile.java中的handleUpdateState方法。去重新获得图标和文本。最后调用

handleStateChanged去回调QSpanel.java 中的代码。

private void handleStateChanged() {
        boolean delayAnnouncement = shouldAnnouncementBeDelayed();
        if (mCallback != null) {
            mCallback.onStateChanged(mState);
            if (mAnnounceNextStateChange && !delayAnnouncement) {
                String announcement = composeChangeAnnouncement();
                if (announcement != null) {
                    mCallback.onAnnouncementRequested(announcement);
                }
            }
        }
        mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
    }
上边标记的橘黄色文本的地方所示,回去调用QSPanel.java中橘黄色代码的地方(当上文中描述QSPanel代码的地方

查看)。最终会调用r.tileView.onStateChanged(state)

r.tileView.onStateChanged(state);

这段代码会去调用QSTileView.java 中的onStateChanged方法。自己去查看onStateChanged源码的话,发现

其实际上是会根据传进来的参数state去更新图标和文本的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值