引言
今天我们主要讲的是SystemUI状态栏里面signal icons中的SignalDrawable,众所周知,signal icons主要用于显示Wifi信号、sim卡信号和飞行模式等等状态以达到提示用户的目的,那么我们今天要讲的主角呢,就是sim卡信号中的信号塔SignalDrawable, SignalDrawable截图如下红框所示
红框中的三角形就是SignalDrawable,代表的是信号塔强度。
正文
本文主要讲述下源码里面SignalDrawable显示的逻辑和加载的流程,话不多说,我们开始吧。
流程图
SignalDrawable显示的逻辑和加载的流程图大致如下
显示的逻辑和加载流程
关于signal icon加载的流程我上一篇已经讲过,SystemUI之状态栏signal icon加载流程
本文不再赘述,我们只要知道SignalDrawable的添加,实际上就是在MobileSignalController中触发的,然后把取到的SignalDrawable.getState状态传递到SignalClusterView中的mMobile.getDrawable().setLevel(mMobileStrengthId)函数完成刷新,我们来简单回顾下
- 显示的逻辑
@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;
}
}
如上显示的逻辑已经讲完,相对来说还是轻车熟路的,接下来就是本文的重点,我们一起看下SignalDrawable里面的实现逻辑。
- 加载流程
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通过位运算带了三个信息(int level, int numLevels, boolean cutOut ),这里我们先复习下功课,回忆下大学的位运算,基本功好的那就跳过吧
1、与运算符 &
知识点:两位同时为“1”,结果才为“1”,否则为“0”。
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
其实就是运算的位要完全一样,才保持原样,否则就变为0。
2、或运算符 |
知识点:只要有一位为1,其值为1,否则位0。
运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
其实就是只要有1,结果就为1。
3、非运算符 ~
知识点:如果位为0,结果是1。如果位为1,结果是0
运算规则:~0=-1; ~1=-2;
非运算也比较简单,网上有很多资料,本文暂未涉及,就算跳过
1 . 场景一(或运算符的使用)
比如我们在xml中布局这样写
android:layout_gravity="bottom|right"
看下源码中的值:
// 0x001 = 0000 0001
int right = 0x001;
// 0x001 = 0000 0010
int bottom = 0x002;
// 结果 = 0000 0011 = 3
System.out.println("right | bottom = " + (right | bottom));
结果是:
right | bottom = 3
通过上面的代码,我们知道其实位错开是为了或运算时,进行值的保留
2 . 场景二(与运算符的使用)
场景一说的是如何组装成一个值,要怎么使用它呢?这时便需要使用 “与” 运算符来 取值。
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));
结果如下:
是否存在 right = true;
是否存在 top = false;
所以我们总结如下:
(1)或运算符整合值
(2)与运算符取值
好了,功课复习完毕,我们回到正题。
前面我们说了getState取到的state通过位运算保存了三个信息(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回调中,把state里面的信息,再通过位运算还原出来,接着通过invalidateSelf触发刷新,我们接着看draw(Canvas canvas) 函数。
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
);
这边就是根据控件的width、height、padding,通过三次的mFullPath.lineTo画线和mFullPath.arcTo转角度,画出了一个空心的三角形。
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);
再根据mLevel和mNumLevels算出一个比例,通过mFullPath画出一个矩形,然后mForegroundPath.op(mFullPath, Op.INTERSECT)函数把三角形和矩形取了一个交集,得到了一个实心的三角形信号塔形状,最后把带颜色的mPaint和mForegroundPaint画到画布上,整个三角形信号塔就画完成了。
到这里,SignalDrawable显示的逻辑和加载的流程已经讲完,如有什么问题欢迎指正。
本文章已经独家授权ApeClub公众号使用。