引言
今天我們主要講的是SystemUI狀態(tài)欄里面signal icons中的SignalDrawable,眾所周知,signal icons主要用于顯示W(wǎng)ifi信號、sim卡信號和飛行模式等等狀態(tài)以達(dá)到提示用戶的目的,那么我們今天要講的主角呢,就是sim卡信號中的信號塔SignalDrawable, SignalDrawable截圖如下紅框所示

紅框中的三角形就是SignalDrawable,代表的是信號塔強(qiáng)度。
正文
本文主要講述下源碼里面SignalDrawable顯示的邏輯和加載的流程,話不多說,我們開始吧。
流程圖
SignalDrawable顯示的邏輯和加載的流程圖大致如下

顯示的邏輯和加載流程
關(guān)于signal icon加載的流程我上一篇已經(jīng)講過,SystemUI之狀態(tài)欄signal icon加載流程
本文不再贅述,我們只要知道SignalDrawable的添加,實際上就是在MobileSignalController中觸發(fā)的,然后把取到的SignalDrawable.getState狀態(tài)傳遞到SignalClusterView中的mMobile.getDrawable().setLevel(mMobileStrengthId)函數(shù)完成刷新,我們來簡單回顧下
- 顯示的邏輯
@Override
public int getCurrentIconId() {
if (mCurrentState.iconGroup == TelephonyIcons.CARRIER_NETWORK_CHANGE) {
return SignalDrawable.getCarrierChangeState(getNumLevels());
} else if (mCurrentState.connected) {
int level = mCurrentState.level;
if (mConfig.inflateSignalStrengths) {
level++;
}
if (mConfig.readIconsFromXml) {
return getIcons().mSingleSignalIcon;
} else {
return SignalDrawable.getState(level, getNumLevels(),
mCurrentState.inetCondition == 0);// 取到正確的state
}
} else if (mCurrentState.enabled) {
if (mConfig.readIconsFromXml) {
return getIcons().mSbDiscState;
} else {
return SignalDrawable.getEmptyState(getNumLevels());
}
} else {
return 0;
}
}
public void setViews(ViewGroup root) {
..............................................
// TODO: Remove the 2 instances because now the drawable can handle darkness.
// mMobile與SignalDrawable完成綁定
mMobile.setImageDrawable(new SignalDrawable(mMobile.getContext()));
SignalDrawable drawable = new SignalDrawable(mMobileDark.getContext());
drawable.setDarkIntensity(1);
mMobileDark.setImageDrawable(drawable);
..............................................
}
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, int dataActivityId,
int stackedDataId, int stackedVoiceId,String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming,
int networkIcon, int volteIcon, boolean showDataIcon) {
PhoneState state = getState(subId);
if (state == null) {
return;
}
state.mMobileVisible = statusIcon.visible && !mBlockMobile;
state.mMobileStrengthId = statusIcon.icon;// 傳遞過來的SignalDrawable.state
state.mMobileTypeId = statusType;
public boolean apply(boolean isSecondaryIcon) {
if (mLastMobileStrengthId != mMobileStrengthId) {
if (mReadIconsFromXML) {
setIconForView(mMobile, mMobileStrengthId);
setIconForView(mMobileDark, mMobileStrengthId);
} else {
mMobile.getDrawable().setLevel(mMobileStrengthId);// 傳遞state給SignalDrawable
mMobileDark.getDrawable().setLevel(mMobileStrengthId);
}
mLastMobileStrengthId = mMobileStrengthId;
}
}
如上顯示的邏輯已經(jīng)講完,相對來說還是輕車熟路的,接下來就是本文的重點(diǎn),我們一起看下SignalDrawable里面的實現(xiàn)邏輯。
- 加載流程
public class SignalDrawable extends Drawable {
private static final int NUM_LEVEL_SHIFT = 8;
private static final int STATE_SHIFT = 16;
// MobileSignalController中的getState
public static int getState(int level, int numLevels, boolean cutOut) {
return ((cutOut ? STATE_CUT : 0) << STATE_SHIFT)
| (numLevels << NUM_LEVEL_SHIFT)
| level;
}
}
從前面的MobileSignalController通過getState取到的state通過位運(yùn)算帶了三個信息(int level, int numLevels, boolean cutOut ),這里我們先復(fù)習(xí)下功課,回憶下大學(xué)的位運(yùn)算,基本功好的那就跳過吧
1、與運(yùn)算符 &
知識點(diǎn):兩位同時為“1”,結(jié)果才為“1”,否則為“0”。
運(yùn)算規(guī)則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
其實就是運(yùn)算的位要完全一樣,才保持原樣,否則就變?yōu)?。
2、或運(yùn)算符 |
知識點(diǎn):只要有一位為1,其值為1,否則位0。
運(yùn)算規(guī)則:0|0=0; 0|1=1; 1|0=1; 1|1=1;
其實就是只要有1,結(jié)果就為1。
3、非運(yùn)算符 ~
知識點(diǎn):如果位為0,結(jié)果是1。如果位為1,結(jié)果是0
運(yùn)算規(guī)則:~0=-1; ~1=-2;
非運(yùn)算也比較簡單,網(wǎng)上有很多資料,本文暫未涉及,就算跳過
1 . 場景一(或運(yùn)算符的使用)
比如我們在xml中布局這樣寫
android:layout_gravity="bottom|right"
看下源碼中的值:
// 0x001 = 0000 0001
int right = 0x001;
// 0x001 = 0000 0010
int bottom = 0x002;
// 結(jié)果 = 0000 0011 = 3
System.out.println("right | bottom = " + (right | bottom));
結(jié)果是:
right | bottom = 3
通過上面的代碼,我們知道其實位錯開是為了或運(yùn)算時,進(jìn)行值的保留
2 . 場景二(與運(yùn)算符的使用)
場景一說的是如何組裝成一個值,要怎么使用它呢?這時便需要使用 “與” 運(yùn)算符來 取值。
int right = 0x001;
int bottom = 0x002;
int top = 0x008;
int state = right | bottom;
System.out.println("是否存在 right = " + ((state & right) == right));
System.out.println("是否存在 top = " + ((state & top) == top));
結(jié)果如下:
是否存在 right = true;
是否存在 top = false;
所以我們總結(jié)如下:
(1)或運(yùn)算符整合值
(2)與運(yùn)算符取值
好了,功課復(fù)習(xí)完畢,我們回到正題。
前面我們說了getState取到的state通過位運(yùn)算保存了三個信息(int level, int numLevels, boolean cutOut ),那么SignalDrawable是在哪里用的呢?
@Override
protected boolean onLevelChange(int state) {
setNumLevels(getNumLevels(state));
setSignalState(getState(state));
int level = getLevel(state);
if (level != mLevel) {
mLevel = level;
invalidateSelf();
}
return true;
}
private static final int LEVEL_MASK = 0xff;
private static final int NUM_LEVEL_SHIFT = 8;
private static final int NUM_LEVEL_MASK = 0xff << NUM_LEVEL_SHIFT;
private static final int STATE_SHIFT = 16;
private static final int STATE_MASK = 0xff << STATE_SHIFT;
public static int getLevel(int fullState) {
return fullState & LEVEL_MASK;
}
public static int getState(int fullState) {
return (fullState & STATE_MASK) >> STATE_SHIFT;
}
public static int getNumLevels(int fullState) {
return (fullState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT;
}
就是這邊,在onLevelChange回調(diào)中,把state里面的信息,再通過位運(yùn)算還原出來,接著通過invalidateSelf觸發(fā)刷新,我們接著看draw(Canvas canvas) 函數(shù)。
final float width = getBounds().width();
final float height = getBounds().height();
mFullPath.reset();
mFullPath.setFillType(FillType.WINDING);
final float padding = Math.round(PAD * width);
final float cornerRadius = RADIUS_RATIO * height;
// Offset from circle where the hypotenuse meets the circle
final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius;
// 1 - Bottom right, above corner
mFullPath.moveTo(width - padding, height - padding - cornerRadius);
// 2 - Line to top right, below corner
mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset);
// 3 - Arc to top right, on hypotenuse
mFullPath.arcTo(
width - padding - (2 * cornerRadius),
padding + mAppliedCornerInset,
width - padding,
padding + mAppliedCornerInset + (2 * cornerRadius),
0.f, -135.f, false
);
// 4 - Line to bottom left, on hypotenuse
mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset,
height - padding - cornerRadius - diagOffset);
// 5 - Arc to bottom left, on leg
mFullPath.arcTo(
padding + mAppliedCornerInset,
height - padding - (2 * cornerRadius),
padding + mAppliedCornerInset + ( 2 * cornerRadius),
height - padding,
-135.f, -135.f, false
);
// 6 - Line to bottom rght, before corner
mFullPath.lineTo(width - padding - cornerRadius, height - padding);
// 7 - Arc to beginning (bottom right, above corner)
mFullPath.arcTo(
width - padding - (2 * cornerRadius),
height - padding - (2 * cornerRadius),
width - padding,
height - padding,
90.f, -90.f, false
);
這邊就是根據(jù)控件的width、height、padding,通過三次的mFullPath.lineTo畫線和mFullPath.arcTo轉(zhuǎn)角度,畫出了一個空心的三角形。
else if (mState != STATE_CARRIER_CHANGE) {
mForegroundPath.reset();
int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding));
mForegroundPath.addRect(padding, padding, padding + sigWidth, height - padding,
Direction.CW);
mForegroundPath.op(mFullPath, Op.INTERSECT);
}
canvas.drawPath(mFullPath, mPaint);
canvas.drawPath(mForegroundPath, mForegroundPaint);
再根據(jù)mLevel和mNumLevels算出一個比例,通過mFullPath畫出一個矩形,然后mForegroundPath.op(mFullPath, Op.INTERSECT)函數(shù)把三角形和矩形取了一個交集,得到了一個實心的三角形信號塔形狀,最后把帶顏色的mPaint和mForegroundPaint畫到畫布上,整個三角形信號塔就畫完成了。
到這里,SignalDrawable顯示的邏輯和加載的流程已經(jīng)講完,如有什么問題歡迎指正。
本文章已經(jīng)獨(dú)家授權(quán)ApeClub公眾號使用。