SystemUI狀態(tài)欄之你容易忽略的SignalDrawable

引言

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


Screenshot_20181115-135430.png

紅框中的三角形就是SignalDrawable,代表的是信號塔強(qiáng)度。

正文

本文主要講述下源碼里面SignalDrawable顯示的邏輯和加載的流程,話不多說,我們開始吧。

流程圖

SignalDrawable顯示的邏輯和加載的流程圖大致如下


SignalDrawable 條件流程圖.png
顯示的邏輯和加載流程

關(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公眾號使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,564評論 0 13
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,245評論 8 265
  • 英語學(xué)習(xí)一直處于零碎的狀態(tài)。直到28天前。 在007,看到伙伴們很多都在用百詞斬。我也試著下載了一個。從此,英語學(xué)...
    曉蕊的世界閱讀 518評論 2 1
  • 一個空元素(empty element)可能是 HTML,SVG,或者 MathML 里的一個不可能存在子節(jié)點(diǎn)(例...
    饑人谷_bibi閱讀 302評論 0 0

友情鏈接更多精彩內(nèi)容