欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android系統(tǒng)狀態(tài)欄定制圖標顯示邏輯控制

 更新時間:2022年10月27日 11:41:04   作者:SugarTurboS  
這篇文章主要為大家介紹了Android系統(tǒng)狀態(tài)欄定制圖標顯示邏輯控制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

背景

項目中為了適應產品形態(tài)需要對Android系統(tǒng)狀態(tài)欄系統(tǒng)圖標以及時鐘和電池等做客制化,滿足不同用戶群體的視覺特性,那在定制過程中需要注意哪些事項?圖標icon是否可以任意大?。繝顟B(tài)欄多顏色模式下圖標如何適配?復雜狀態(tài)圖標如何調整邏輯?

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

首先來看下狀態(tài)欄載體是什么?狀態(tài)欄本質其實就是一個懸浮窗,在systemui初始化時創(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)建的一個懸浮窗。而這個布局包含了狀態(tài)欄所有內容,應用通知,系統(tǒng)圖標,時鐘等。其主體內容如下

<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,在代碼中通過fragmentmanager替換了了這個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的實現(xiàn)就是加載了status_bar.xml 這個布局。

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

status_bar.xml 布局內容就是顯示出來的狀態(tài)欄布局。這樣狀態(tài)欄整體布局就比較清晰,包含了應用通知,系統(tǒng)圖標, 時鐘,電池等。

<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"
        ...
        <!-- 左側顯示區(qū)域 整體權重只占了1-->
        <FrameLayout
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1">
            ...
            <LinearLayout
                android:id="@+id/status_bar_left_side"
                ...             
                >
                <!-- 時鐘 -->
                <com.android.systemui.statusbar.policy.Clock
                    android:id="@+id/clock"
                    ...
                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                 />
                <!-- 應用通知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實際顯示布局 -->
            <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>

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

其中我們需要定制的從UI設計稿中可以看出,是三個區(qū)域,時鐘, 系統(tǒng)icon,電池, 應用通知在這個項目中不需要,可以直接去掉通知信息功能,就不會顯示出來。clock和battery都是自定義控件,比較好處理。重點看下系統(tǒng)icon實現(xiàn)。

系統(tǒng)ICON布局

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

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

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

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

public boolean hasOverlappingRendering() {  
    return false;  
}

該方法用來標記當前view是否存在過度繪制,存在返回ture,不存在返回false,默認返回為true。 在android的View里有透明度的屬性,當設置透明度setAlpha的時候,android里默認會把當前view繪制到offscreen buffer中,然后再顯示出來。 這個offscreen buffer 可以理解為一個臨時緩沖區(qū),把當前View放進來并做透明度的轉化,然后在顯示到屏幕上。這個過程是消耗資源的,所以應該盡量避免這個過程。而當繼承了hasOverlappingRendering()方法返回false后,android會自動進行合理的優(yōu)化,避免使用offscreen buffer。

系統(tǒng)icon繪制流程會比較多。 先從頂層view StatusIconContainer的繪制來分析。view的繪制離不開三個步驟,onMeasure, onLayout, onDraw,現(xiàn)在來一一拆解查看。

StatusIconContainer -- onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 獲取到所有需要展示的view,注意看不可見的,icon處于blocked狀態(tài)的,
        // 還有需要忽略的都不會被加入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();
        //  計算最大可見的icon數(shù)量,默認為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);
            //測量每個childview的寬
            measureChild(child, childWidthSpec, heightMeasureSpec);
            if (mShouldRestrictIcons) {
                // 計算總的寬度
                if (i < maxVisible && trackWidth) {
                    totalWidth += getViewTotalMeasuredWidth(child);
                } else if (trackWidth) {
                    // 超過最大可見數(shù)量時 需要給省略點計算空間。
                    totalWidth += mUnderflowWidth;
                    trackWidth = false;
                }
            } else {
                totalWidth += getViewTotalMeasuredWidth(child);
            }
        }
        // 通過setMeasuredDimension設置view的寬高
        if (mode == MeasureSpec.EXACTLY) {
            ...
            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
        } else {
            ...
            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
        }
    }

從上面可以看出來, onMeaure主要時計算每個子view的寬高,并計算出父view的整的寬度,其中會給超過最大數(shù)量的情況下 計算省略點的寬度,可以視項目情況來決定這個省略點的數(shù)量,其可在代碼中通過常量來自定義。

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);
        }
        // 重置每個view的狀態(tài)。通過StatusIconState重置狀態(tài)
        resetViewStates();
        // 重新依據(jù)實際情況計算每個icon的顯示狀態(tài),下面單獨拎出來講。
        calculateIconTranslations();
        // 應用view的狀態(tài),包含icon顯示的動畫。
        applyIconStates();
    }

onLayou常規(guī)是計算每個view的寬高,并按預定的規(guī)則排放,然后計算每個view的位置。calculateIconTranslations顯示邏輯會比較多,單獨拎出來講:

    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;
            // 位置顯示的關鍵點, translationX 初始值是整個view的寬度,這樣計算每個view
            // 的實際布局位置
            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
            // 這里比較關鍵 從列表中逆序獲取到每個view的位置,如果view的xTranslation 下雨
            // 小于顯示的內容就停止,后續(xù)就從這個index開始繪制
            if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
                    (mShouldRestrictIcons && visible >= maxVisible)) {
                firstUnderflowIndex = i;
                break;
            }
            mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
            visible++;
        }
        //后續(xù)邏輯就是配置是否顯示icon和顯示多少個dot
        ...
    }

onLayout邏輯較多,簡單來說就是通過每個子view的xTranslation和整體的view空間,計算需要顯示多少icon,同時要給省略點預留空間。簡單示意如下??赡艹^空間的就用dot來顯示。

StatusIconContainer -- onDraw

這塊沒有定制處理,只是做了debug的一些信息繪制.

至此系統(tǒng)icon的頂層view分析完成,其主要是通過子view的狀態(tài)以及父view的空間等情況來決定是否需要顯示哪些icon,以及顯示省略點符號。接下來再看每個子view的情況。

子view就是顯示狀態(tài)欄上icon,但是其封裝了一層繼承自AnimatedImageView,帶動畫效果的ImageView。

子view的具體實現(xiàn) StatusBarIconView

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

挑其中重點解析。圖片的縮放,怎么把任意圖片的大小適合在狀態(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的情況下,通過獲取實際的icon的大小, 計算出 mIconScale.

在onDraw的時候 通過canvas.scale 把畫布以icon的中心點根據(jù)mIconScale縮放到system_icon_size. 但是這樣存在一個問題,icon的實際大小還是原大小,只是顯示小了。其它部分包含動畫就不再細講。

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

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

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

狀態(tài)欄圖標顯示邏輯是通過 StatusBarIconControllerImpl 這個類來實現(xiàn)管理, 在對象構造的時候默認初始化

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

config_statusBarIcons 這個array中包含了所有支持的icon。如有需要定制圖標順序可在這個列表中對圖標對應的item進行調整。

   <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字串信息當做一個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;
        ...
}

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

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

關鍵view管理

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

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

DarkIconManager 構造函數(shù)中傳入了system_icon的容器viewgroup, 負責view的增加和刪除。StatusBarIconController管理DarkIconManager. 這樣顯示圖標區(qū)域控制部分與顯示部分關聯(lián)起來。

圖標如何更新?

控制管理的實現(xiàn)策略類都在 PhoneStatusBarPolicy 這個里面實現(xiàn)。 具體實現(xiàn)通過StatusBarIconControllerImpl類實現(xiàn),可以通過如下接口更新顯示圖標。

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

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

    @Override
    public void setIcon(String slot, int resourceId, CharSequence contentDescription) {        
//  檢查是否在列表中存在holder,默認初始情況下是都沒有holder的,需要新建
        int index = getSlotIndex(slot);
        StatusBarIconHolder holder = getIcon(index, 0);
        if (holder == null) {
            先通過resoureid和 contentDescription創(chuàng)建一個StatusBarIcon實例
            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                    Icon.createWithResource(
                            mContext, resourceId), 0, 0, contentDescription);
            // 通過icon封裝一個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為描述字串。如果判斷為沒有holder就會新建一個holder類,并傳入mSlots的列表中。

    @Override
    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
        boolean isNew = getIcon(index, holder.getTag()) == null;
        super.setIcon(index, holder);
        if (isNew) { 
            // 通過tag判斷如果是新的就加到systemicon中
            addSystemIcon(index, holder);
        } else {
            //已經存在的直接設置
            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中實現(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)圖標的容器view。這里就完成了view的添加
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
        }

這樣就通過setIcon把圖標添加到了系統(tǒng)圖標區(qū),然后再通過setIconVisibility顯示出圖標。顯示的邏輯和setIcon差不多,只是增加了visible狀態(tài),可以自行分析。更新過程中有兩個特殊的圖標,wifi和數(shù)據(jù)網絡,其狀態(tài)會包含多個,正常都是只有顯示與否邏輯,所以這里邏輯會多一些,但是原理一樣的。

至此就完成了整個系統(tǒng)圖標顯示控制分析。

如何定制?

數(shù)量和順序 通過配置 config_statusBarIcons 增刪自己需要的圖標。

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

配置項說明
status_bar_height_portrait狀態(tài)欄高度
status_bar_system_icon_intrinsic_size系統(tǒng)圖標期望大小, 用于icon的縮放,和icon_size設置一樣大小即可
status_bar_system_icon_size系統(tǒng)圖標大小

SystemUI/res/values/dimens.xml

配置項說明
status_bar_padding_start狀態(tài)欄離左側空間
status_bar_padding_end狀態(tài)欄離右側空間
signal_cluster_battery_padding系統(tǒng)圖標離電池圖標距離

圖標顯示定制 通過上述分析代碼 setIcon找到對應的icon進行替換自己項目的icon, StatusIconContainer中需要修改MAX_DOTS為0,不顯示省略點。再onLayout的時候需要根據(jù)項目設置icon的間距, child.layout中增加r值。

顯示邏輯策略都在PhoneStatusBarPhicy中實現(xiàn),尤其是系統(tǒng)原生沒有支持的圖標邏輯會定制較多。

注意事項:

圖標選擇,使用新的svg圖標時, 寬高最好和系統(tǒng)system_icon_size設置為一致,原生邏輯會把圖標縮放到高度和system_icon一致,倒是寬度卻保持了原有圖標寬,導致顯示布局不對。

以上就是Android系統(tǒng)狀態(tài)欄定制圖標顯示邏輯控制的詳細內容,更多關于Android 狀態(tài)欄圖標的資料請關注腳本之家其它相關文章!

相關文章

最新評論