SystemUI状态栏之你容易忽略的SignalDrawable

引言

今天我们主要讲的是SystemUI状态栏里面signal icons中的SignalDrawable,众所周知,signal icons主要用于显示Wifi信号、sim卡信号和飞行模式等等状态以达到提示用户的目的,那么我们今天要讲的主角呢,就是sim卡信号中的信号塔SignalDrawable, SignalDrawable截图如下红框所示


Screenshot_20181115-135430.png

红框中的三角形就是SignalDrawable,代表的是信号塔强度。

正文

本文主要讲述下源码里面SignalDrawable显示的逻辑和加载的流程,话不多说,我们开始吧。

流程图

SignalDrawable显示的逻辑和加载的流程图大致如下


SignalDrawable 条件流程图.png
显示的逻辑和加载流程

关于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公众号使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,299评论 8 265
  • 英语学习一直处于零碎的状态。直到28天前。 在007,看到伙伴们很多都在用百词斩。我也试着下载了一个。从此,英语学...
    晓蕊的世界阅读 452评论 2 1
  • 一个空元素(empty element)可能是 HTML,SVG,或者 MathML 里的一个不可能存在子节点(例...
    饥人谷_bibi阅读 265评论 0 0