Android系統(tǒng)狀態(tài)欄定制

背景

項(xiàng)目中為了適應(yīng)產(chǎn)品形態(tài)需要對(duì)Android系統(tǒng)狀態(tài)欄系統(tǒng)圖標(biāo)以及時(shí)鐘和電池等做客制化,滿足不同用戶群體的視覺(jué)特性应又,那在定制過(guò)程中需要注意哪些事項(xiàng)?圖標(biāo)icon是否可以任意大小?狀態(tài)欄多顏色模式下圖標(biāo)如何適配窍霞?復(fù)雜狀態(tài)圖標(biāo)如何調(diào)整邏輯?

狀態(tài)欄是什么拯坟?

首先來(lái)看下?tīng)顟B(tài)欄載體是什么但金?狀態(tài)欄本質(zhì)其實(shí)就是一個(gè)懸浮窗傀广,在systemui初始化時(shí)創(chuàng)建顯示都办。SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

protected void inflateStatusBarWindow(Context context) {
    mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
            LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}

由上可知狀態(tài)欄就是使用super_status_bar.xml布局創(chuàng)建的一個(gè)懸浮窗。而這個(gè)布局包含了狀態(tài)欄所有內(nèi)容乓土,應(yīng)用通知梦裂,系統(tǒng)圖標(biāo)似枕,時(shí)鐘等。其主體內(nèi)容如下

<com.android.systemui.statusbar.phone.StatusBarWindowView
    ...
    <FrameLayout
        android:id="@+id/status_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    ...
</com.android.systemui.statusbar.phone.StatusBarWindowView>

其中包含status_bar_container 的framelayout的容器即為狀態(tài)欄的view年柠,在代碼中通過(guò)fragmentmanager替換了了這個(gè)container凿歼。

    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        ...
        FragmentHostManager.get(mStatusBarWindow)
                .addTagListener(...).getFragmentManager()
                .beginTransaction()
                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                        CollapsedStatusBarFragment.TAG)
                .commit();

而CollapsedStatusBarFragment的實(shí)現(xiàn)就是加載了status_bar.xml 這個(gè)布局。

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.status_bar, container, false);
    }

status_bar.xml 布局內(nèi)容就是顯示出來(lái)的狀態(tài)欄布局冗恨。這樣狀態(tài)欄整體布局就比較清晰答憔,包含了應(yīng)用通知,系統(tǒng)圖標(biāo), 時(shí)鐘派近,電池等。

<com.android.systemui.statusbar.phone.PhoneStatusBarView
    ...
    android:layout_height="@dimen/status_bar_height"
    android:id="@+id/status_bar"
    ...
    >
    ...
    <LinearLayout android:id="@+id/status_bar_contents"
        ...
        <!-- 左側(cè)顯示區(qū)域 整體權(quán)重只占了1-->
        <FrameLayout
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1">
            ...
            <LinearLayout
                android:id="@+id/status_bar_left_side"
                ...             
                >
                <!-- 時(shí)鐘 -->
                <com.android.systemui.statusbar.policy.Clock
                    android:id="@+id/clock"
                    ...
                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                 />
                <!-- 應(yīng)用通知icon區(qū)域 -->
                <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                    android:id="@+id/notification_icon_area"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:orientation="horizontal"
                    android:clipChildren="false"/>

            </LinearLayout>
        </FrameLayout>
        ...
        <!-- 中間icon顯示區(qū)域 -->
        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/centered_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipChildren="false"
            android:gravity="center_horizontal|center_vertical"/>

        <!-- 系統(tǒng)icon顯示區(qū)域-->
        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:gravity="center_vertical|end"
            >
            <!-- 系統(tǒng)icon實(shí)際顯示布局 -->
            <include layout="@layout/system_icons" />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>
...
</com.android.systemui.statusbar.phone.PhoneStatusBarView>

系統(tǒng)icon區(qū)域 system_icons.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/system_icons"
    ...>

    <com.android.systemui.statusbar.phone.StatusIconContainer 
        android:id="@+id/statusIcons"
        android:layout_width="0dp"
        android:layout_weight="1"
        .../>

    <com.android.systemui.statusbar.phone.seewo.BatteryImageView
        android:id="@+id/battery"
        .../>
</LinearLayout>

整個(gè)狀態(tài)欄整體布局示意如下:

其中我們需要定制的從UI設(shè)計(jì)稿中可以看出洁桌,是三個(gè)區(qū)域渴丸,時(shí)鐘, 系統(tǒng)icon另凌,電池谱轨, 應(yīng)用通知在這個(gè)項(xiàng)目中不需要,可以直接去掉通知信息功能吠谢,就不會(huì)顯示出來(lái)土童。clock和battery都是自定義控件,比較好處理工坊。重點(diǎn)看下系統(tǒng)icon實(shí)現(xiàn)献汗。

系統(tǒng)ICON布局

由上客制系統(tǒng)圖標(biāo)區(qū)域包含一個(gè)statusIcons 的容器view,還有battery 顯示view王污。

其布局也是自定義view, StatusIconContainer.java

    <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
        android:layout_width="0dp"
        .../>

其實(shí)現(xiàn)是基于AlphaOptimizedLinearLayout布局實(shí)現(xiàn)的一個(gè)自定義布局罢吃。AlphaOptimizedLinearLayout是繼承自LinearLayout只是覆蓋了

public boolean hasOverlappingRendering() {  
    return false;  
}

該方法用來(lái)標(biāo)記當(dāng)前view是否存在過(guò)度繪制,存在返回ture昭齐,不存在返回false尿招,默認(rèn)返回為true。 在android的View里有透明度的屬性,當(dāng)設(shè)置透明度setAlpha的時(shí)候就谜,android里默認(rèn)會(huì)把當(dāng)前view繪制到offscreen buffer中怪蔑,然后再顯示出來(lái)。 這個(gè)offscreen buffer 可以理解為一個(gè)臨時(shí)緩沖區(qū)丧荐,把當(dāng)前View放進(jìn)來(lái)并做透明度的轉(zhuǎn)化缆瓣,然后在顯示到屏幕上。這個(gè)過(guò)程是消耗資源的篮奄,所以應(yīng)該盡量避免這個(gè)過(guò)程捆愁。而當(dāng)繼承了hasOverlappingRendering()方法返回false后,android會(huì)自動(dòng)進(jìn)行合理的優(yōu)化窟却,避免使用offscreen buffer昼丑。

系統(tǒng)icon繪制流程會(huì)比較多。 先從頂層view StatusIconContainer的繪制來(lái)分析夸赫。view的繪制離不開(kāi)三個(gè)步驟菩帝,onMeasure, onLayout, onDraw,現(xiàn)在來(lái)一一拆解查看茬腿。

StatusIconContainer -- onMeasure
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 獲取到所有需要展示的view呼奢,注意看不可見(jiàn)的,icon處于blocked狀態(tài)的切平,
        // 還有需要忽略的都不會(huì)被加入mMeasureViews中
        for (int i = 0; i < count; i++) {
            StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
            if (icon.isIconVisible() && !icon.isIconBlocked()
                    && !mIgnoredSlots.contains(icon.getSlot())) {
                mMeasureViews.add((View) icon);
            }
        }

        int visibleCount = mMeasureViews.size();
        //  計(jì)算最大可見(jiàn)的icon數(shù)量握础,默認(rèn)為7
        int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
        int totalWidth = mPaddingLeft + mPaddingRight;
        boolean trackWidth = true;

        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
        mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
        for (int i = 0; i < mMeasureViews.size(); i++) {
            // Walking backwards
            View child = mMeasureViews.get(visibleCount - i - 1);
            //測(cè)量每個(gè)childview的寬
            measureChild(child, childWidthSpec, heightMeasureSpec);
            if (mShouldRestrictIcons) {
                // 計(jì)算總的寬度
                if (i < maxVisible && trackWidth) {
                    totalWidth += getViewTotalMeasuredWidth(child);
                } else if (trackWidth) {
                    // 超過(guò)最大可見(jiàn)數(shù)量時(shí) 需要給省略點(diǎn)計(jì)算空間。
                    totalWidth += mUnderflowWidth;
                    trackWidth = false;
                }
            } else {
                totalWidth += getViewTotalMeasuredWidth(child);
            }
        }
        // 通過(guò)setMeasuredDimension設(shè)置view的寬高
        if (mode == MeasureSpec.EXACTLY) {
            ...
            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
        } else {
            ...
            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
        }
    }

從上面可以看出來(lái)悴品, onMeaure主要時(shí)計(jì)算每個(gè)子view的寬高禀综,并計(jì)算出父view的整的寬度,其中會(huì)給超過(guò)最大數(shù)量的情況下 計(jì)算省略點(diǎn)的寬度苔严,可以視項(xiàng)目情況來(lái)決定這個(gè)省略點(diǎn)的數(shù)量定枷,其可在代碼中通過(guò)常量來(lái)自定義。

StatusIconContainer -- onLayout
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        float midY = getHeight() / 2.0f;

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();
            int top = (int) (midY - height / 2.0f);
            child.layout(0, top, width, top + height);
        }
        // 重置每個(gè)view的狀態(tài)届氢。通過(guò)StatusIconState重置狀態(tài)
        resetViewStates();
        // 重新依據(jù)實(shí)際情況計(jì)算每個(gè)icon的顯示狀態(tài)欠窒,下面單獨(dú)拎出來(lái)講。
        calculateIconTranslations();
        // 應(yīng)用view的狀態(tài)退子,包含icon顯示的動(dòng)畫(huà)岖妄。
        applyIconStates();
    }

onLayou常規(guī)是計(jì)算每個(gè)view的寬高,并按預(yù)定的規(guī)則排放寂祥,然后計(jì)算每個(gè)view的位置衣吠。calculateIconTranslations顯示邏輯會(huì)比較多,單獨(dú)拎出來(lái)講:

    private void calculateIconTranslations() {
        mLayoutStates.clear();
        ...
        // 
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            StatusIconDisplayable iconView = (StatusIconDisplayable) child;
            StatusIconState childState = getViewStateFromChild(child);

            if (!iconView.isIconVisible() || iconView.isIconBlocked()
                    || mIgnoredSlots.contains(iconView.getSlot())) {
                childState.visibleState = STATE_HIDDEN;
                if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
                continue;
            }

            childState.visibleState = STATE_ICON;
            // 位置顯示的關(guān)鍵點(diǎn)壤靶, translationX 初始值是整個(gè)view的寬度缚俏,這樣計(jì)算每個(gè)view
            // 的實(shí)際布局位置
            childState.xTranslation = translationX - getViewTotalWidth(child);
            mLayoutStates.add(0, childState);
            translationX -= getViewTotalWidth(child);
        }

        // Show either 1-MAX_ICONS icons, or (MAX_ICONS - 1) icons + overflow
        int totalVisible = mLayoutStates.size();
        int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;

        mUnderflowStart = 0;
        int visible = 0;
        int firstUnderflowIndex = -1;
        for (int i = totalVisible - 1; i >= 0; i--) {
            StatusIconState state = mLayoutStates.get(i);
            // Allow room for underflow if we found we need it in onMeasure
            // 這里比較關(guān)鍵 從列表中逆序獲取到每個(gè)view的位置,如果view的xTranslation 下雨
            // 小于顯示的內(nèi)容就停止,后續(xù)就從這個(gè)index開(kāi)始繪制
            if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
                    (mShouldRestrictIcons && visible >= maxVisible)) {
                firstUnderflowIndex = i;
                break;
            }
            mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
            visible++;
        }

        //后續(xù)邏輯就是配置是否顯示icon和顯示多少個(gè)dot
        ...
    }

onLayout邏輯較多忧换,簡(jiǎn)單來(lái)說(shuō)就是通過(guò)每個(gè)子view的xTranslation和整體的view空間恬惯,計(jì)算需要顯示多少icon,同時(shí)要給省略點(diǎn)預(yù)留空間亚茬。簡(jiǎn)單示意如下酪耳。可能超過(guò)空間的就用dot來(lái)顯示刹缝。

StatusIconContainer -- onDraw

這塊沒(méi)有定制處理碗暗,只是做了debug的一些信息繪制.

至此系統(tǒng)icon的頂層view分析完成,其主要是通過(guò)子view的狀態(tài)以及父view的空間等情況來(lái)決定是否需要顯示哪些icon梢夯,以及顯示省略點(diǎn)符號(hào)言疗。接下來(lái)再看每個(gè)子view的情況。

子view就是顯示狀態(tài)欄上icon颂砸,但是其封裝了一層繼承自AnimatedImageView噪奄,帶動(dòng)畫(huà)效果的ImageView。 子view的具體實(shí)現(xiàn) StatusBarIconView

SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconView.java

挑其中重點(diǎn)解析人乓。圖片的縮放勤篮,怎么把任意圖片的大小適合在狀態(tài)欄顯示。

    private void updateIconScaleForSystemIcons() {
        float iconHeight = getIconHeight();
        if (iconHeight != 0) {
            mIconScale = mSystemIconDesiredHeight / iconHeight;
        } else {
            mIconScale = mSystemIconDefaultScale;
        }
    }

先獲取到mIconScale需要縮放的比例色罚,mSystemIconDesiredHeight 是配置的全局的system icon的大小碰缔。

        mSystemIconDesiredHeight = res.getDimension(
                com.android.internal.R.dimen.status_bar_system_icon_size);

存在icon的情況下,通過(guò)獲取實(shí)際的icon的大小戳护, 計(jì)算出 mIconScale.

在onDraw的時(shí)候 通過(guò)canvas.scale 把畫(huà)布以icon的中心點(diǎn)根據(jù)mIconScale縮放到system_icon_size. 但是這樣存在一個(gè)問(wèn)題金抡,icon的實(shí)際大小還是原大小,只是顯示小了姑尺。其它部分包含動(dòng)畫(huà)就不再細(xì)講竟终。

    @Override
    protected void onDraw(Canvas canvas) {
        if (mIconAppearAmount > 0.0f) {
            canvas.save();
            canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
                    getWidth() / 2, getHeight() / 2);
            super.onDraw(canvas);
            canvas.restore();
        }
        ...
    }

到這里狀態(tài)欄布局以及系統(tǒng)圖標(biāo)的view繪制大體分析完成蝠猬。 接下來(lái)看icon是怎么控制添加切蟋,刪除以及更新的。

狀態(tài)欄圖標(biāo)顯示邏輯控制

狀態(tài)欄圖標(biāo)顯示邏輯是通過(guò) StatusBarIconControllerImpl 這個(gè)類來(lái)實(shí)現(xiàn)管理榆芦, 在對(duì)象構(gòu)造的時(shí)候默認(rèn)初始化

    public StatusBarIconControllerImpl(Context context) {
        super(context.getResources().getStringArray(
                com.android.internal.R.array.config_statusBarIcons));
    ...
   }

config_statusBarIcons 這個(gè)array中包含了所有支持的icon柄粹。如有需要定制圖標(biāo)順序可在這個(gè)列表中對(duì)圖標(biāo)對(duì)應(yīng)的item進(jìn)行調(diào)整。

   <string-array name="config_statusBarIcons">
        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
        ...
        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
   </string-array>

這些 icon字串信息當(dāng)做一個(gè)title信息匆绣,保存在mSlots列表中驻右。而Slot中包含StatusBarIconHolder:

    public static class Slot {
        private final String mName;
        private StatusBarIconHolder mHolder;
        ...
    }
public class StatusBarIconHolder {
    public static final int TYPE_ICON = 0;
    public static final int TYPE_WIFI = 1;
    public static final int TYPE_MOBILE = 2;

    private StatusBarIcon mIcon;
    private WifiIconState mWifiState;
    private MobileIconState mMobileState;
        ...
}

啟動(dòng)mIcon即為顯示的圖標(biāo)資源保存類。其中包含了圖標(biāo)顯示狀態(tài)崎淳,標(biāo)簽信息以及狀態(tài)信息等堪夭。將其都保存在mSlogs的列表中,方便管理顯示。

總結(jié)數(shù)據(jù)保存鏈條 Slots--> StatusBarIconHolder --> StatusBarIcon;

關(guān)鍵view管理

在狀態(tài)欄初始化的時(shí)候 CollapsedStatusBarFragment 的view創(chuàng)建中 onViewCreated對(duì)系統(tǒng)icon管理進(jìn)行初始化森爽。

        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
        mDarkIconManager.setShouldLog(true);
        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);

DarkIconManager 構(gòu)造函數(shù)中傳入了system_icon的容器viewgroup, 負(fù)責(zé)view的增加和刪除恨豁。StatusBarIconController管理DarkIconManager. 這樣顯示圖標(biāo)區(qū)域控制部分與顯示部分關(guān)聯(lián)起來(lái)。

圖標(biāo)如何更新爬迟?

控制管理的實(shí)現(xiàn)策略類都在 PhoneStatusBarPolicy 這個(gè)里面實(shí)現(xiàn)橘蜜。 具體實(shí)現(xiàn)通過(guò)StatusBarIconControllerImpl類實(shí)現(xiàn),可以通過(guò)如下接口更新顯示圖標(biāo)付呕。

mIconController.setIcon(mSlotVolume, volumeIconId, volumeDescription);
mIconController.setIconVisibility(mSlotVolume, volumeVisible);

以音量更新為例计福。setIcon流程分析。

    @Override
    public void setIcon(String slot, int resourceId, CharSequence contentDescription) {        
//  檢查是否在列表中存在holder徽职,默認(rèn)初始情況下是都沒(méi)有holder的象颖,需要新建
        int index = getSlotIndex(slot);
        StatusBarIconHolder holder = getIcon(index, 0);
        if (holder == null) {
            先通過(guò)resoureid和 contentDescription創(chuàng)建一個(gè)StatusBarIcon實(shí)例
            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                    Icon.createWithResource(
                            mContext, resourceId), 0, 0, contentDescription);
            // 通過(guò)icon封裝一個(gè)holder。
            holder = StatusBarIconHolder.fromIcon(icon);
            // 將holder賦值給mSlots
            setIcon(index, holder);
        } else {
            holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
            holder.getIcon().contentDescription = contentDescription;
            handleSet(index, holder);
        }
    }

傳入slot為icon的title活箕, resourceId為資源文件力麸,contentDescription為描述字串。如果判斷為沒(méi)有holder就會(huì)新建一個(gè)holder類育韩,并傳入mSlots的列表中克蚂。

    @Override
    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
        boolean isNew = getIcon(index, holder.getTag()) == null;
        super.setIcon(index, holder);

        if (isNew) { 
            // 通過(guò)tag判斷如果是新的就加到systemicon中
            addSystemIcon(index, holder);
        } else {
            //已經(jīng)存在的直接設(shè)置
            handleSet(index, holder);
        }
    }
    private void addSystemIcon(int index, StatusBarIconHolder holder) {
        String slot = getSlotName(index);
        int viewIndex = getViewIndex(index, holder.getTag());
        boolean blocked = mIconBlacklist.contains(slot);

        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
    }

onIconAdded是在DarkIconManager中實(shí)現(xiàn)。

        protected void onIconAdded(int index, String slot, boolean blocked,
                StatusBarIconHolder holder) {
            addHolder(index, slot, blocked, holder);
        }
        protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
                StatusBarIconHolder holder) {
            switch (holder.getType()) {
                case TYPE_ICON:
                    return addIcon(index, slot, blocked, holder.getIcon());

                case TYPE_WIFI:
                    return addSignalIcon(index, slot, holder.getWifiState());

                case TYPE_MOBILE:
                    return addMobileIcon(index, slot, holder.getMobileState());
            }

            return null;
        }
        protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
                StatusBarIcon icon) {
            StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
            view.set(icon); //mGroup 即為狀態(tài)欄系統(tǒng)圖標(biāo)的容器view筋讨。這里就完成了view的添加
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
        }

這樣就通過(guò)setIcon把圖標(biāo)添加到了系統(tǒng)圖標(biāo)區(qū)埃叭,然后再通過(guò)setIconVisibility顯示出圖標(biāo)。顯示的邏輯和setIcon差不多悉罕,只是增加了visible狀態(tài)赤屋,可以自行分析。更新過(guò)程中有兩個(gè)特殊的圖標(biāo)壁袄,wifi和數(shù)據(jù)網(wǎng)絡(luò)类早,其狀態(tài)會(huì)包含多個(gè),正常都是只有顯示與否邏輯嗜逻,所以這里邏輯會(huì)多一些涩僻,但是原理一樣的。

至此就完成了整個(gè)系統(tǒng)圖標(biāo)顯示控制分析栈顷。

如何定制逆日?

  1. 數(shù)量和順序 通過(guò)配置 config_statusBarIcons 增刪自己需要的圖標(biāo)。

  2. 狀態(tài)欄大小定制 framework/base/core/res/res/values/dimens.xml

    配置項(xiàng) 說(shuō)明
    status_bar_height_portrait 狀態(tài)欄高度
    status_bar_system_icon_intrinsic_size 系統(tǒng)圖標(biāo)期望大小萄凤, 用于icon的縮放室抽,和icon_size設(shè)置一樣大小即可
    status_bar_system_icon_size 系統(tǒng)圖標(biāo)大小

    SystemUI/res/values/dimens.xml

    配置項(xiàng) 說(shuō)明
    status_bar_padding_start 狀態(tài)欄離左側(cè)空間
    status_bar_padding_end 狀態(tài)欄離右側(cè)空間
    signal_cluster_battery_padding 系統(tǒng)圖標(biāo)離電池圖標(biāo)距離
  3. 圖標(biāo)顯示定制 通過(guò)上述分析代碼 setIcon找到對(duì)應(yīng)的icon進(jìn)行替換自己項(xiàng)目的icon, StatusIconContainer中需要修改MAX_DOTS為0靡努,不顯示省略點(diǎn)坪圾。再onLayout的時(shí)候需要根據(jù)項(xiàng)目設(shè)置icon的間距晓折, child.layout中增加r值。

  4. 顯示邏輯策略都在PhoneStatusBarPhicy中實(shí)現(xiàn)兽泄,尤其是系統(tǒng)原生沒(méi)有支持的圖標(biāo)邏輯會(huì)定制較多已维。

注意事項(xiàng):

  1. 圖標(biāo)選擇,使用新的svg圖標(biāo)時(shí)已日, 寬高最好和系統(tǒng)system_icon_size設(shè)置為一致垛耳,原生邏輯會(huì)把圖標(biāo)縮放到高度和system_icon一致,倒是寬度卻保持了原有圖標(biāo)寬飘千,導(dǎo)致顯示布局不對(duì)堂鲜。

作者:SugarTurboS
鏈接:https://juejin.cn/post/7157878498525184013

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市护奈,隨后出現(xiàn)的幾起案子缔莲,更是在濱河造成了極大的恐慌,老刑警劉巖霉旗,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痴奏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡厌秒,警方通過(guò)查閱死者的電腦和手機(jī)读拆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鸵闪,“玉大人檐晕,你說(shuō)我怎么就攤上這事“鏊希” “怎么了辟灰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)篡石。 經(jīng)常有香客問(wèn)我芥喇,道長(zhǎng),這世上最難降的妖魔是什么凰萨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任继控,我火速辦了婚禮,結(jié)果婚禮上沟蔑,老公的妹妹穿的比我還像新娘湿诊。我一直安慰自己狱杰,他們只是感情好瘦材,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著仿畸,像睡著了一般食棕。 火紅的嫁衣襯著肌膚如雪朗和。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天簿晓,我揣著相機(jī)與錄音眶拉,去河邊找鬼。 笑死憔儿,一個(gè)胖子當(dāng)著我的面吹牛忆植,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谒臼,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼朝刊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蜈缤?” 一聲冷哼從身側(cè)響起拾氓,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎底哥,沒(méi)想到半個(gè)月后咙鞍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趾徽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年续滋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孵奶。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吃粒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拒课,到底是詐尸還是另有隱情徐勃,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布早像,位于F島的核電站僻肖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏卢鹦。R本人自食惡果不足惜臀脏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冀自。 院中可真熱鬧揉稚,春花似錦、人聲如沸熬粗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)驻呐。三九已至灌诅,卻和暖如春芳来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猜拾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工即舌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挎袜。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓顽聂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盯仪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芜飘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容