Android system status bar customization

What is the status bar?

First of all, what is the status bar carrier? The essence of the status bar is actually a floating window, which is created and displayed when systemui is initialized.

frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\window\StatusBarWindowModule.kt

@Module
abstract class StatusBarWindowModule {
    /**
     * Provides a [StatusBarWindowView].
     *
     * Only [StatusBarWindowController] should inject the view.
     */
    @Module
    companion object {
        @JvmStatic
        @Provides
        @SysUISingleton
        @InternalWindowView
        fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView {
            return layoutInflater.inflate(
                R.layout.super_status_bar,
                /* root= */null
            ) as StatusBarWindowView?
                ?: throw IllegalStateException(
                    "R.layout.super_status_bar could not be properly inflated"
                )
        }
    }

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);
}

It can be seen from the above that the status bar is a floating window created using the super_status_bar.xml layout. And this layout contains all the content of the status bar, application notifications, system icons, clock, etc. Its main content is as follows

<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>

The framelayout container containing status_bar_container is the view of the status bar, and the container is replaced by fragmentmanager in the code.

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

The implementation of CollapsedStatusBarFragment is to load the status_bar.xml layout.

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

The status_bar.xml layout content is the displayed status bar layout. In this way, the overall layout of the status bar is relatively clear, including application notifications, system icons, clock, battery, etc.

<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"
        ...
        <!-- 左侧显示区域 整体权重只占了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区域 -->
                <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显示区域 -->
        <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"/>

        <!-- 系统icon显示区域-->
        <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"
            >
            <!-- 系统icon实际显示布局 -->
            <include layout="@layout/system_icons" />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>
...
</com.android.systemui.statusbar.phone.PhoneStatusBarView>

System icon area 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>

The overall layout of the status bar is as follows:

Among them, what we need to customize can be seen from the UI design draft, which are three areas, the clock, the system icon, the battery, and the application notification are not needed in this project. You can directly remove the notification information function, and it will not be displayed. Both clock and battery are custom controls, which are easier to handle. Focus on the system icon implementation.

System ICON layout

The customized system icon area includes a statusIcons container view and a battery display view.

Its layout is also a custom view, StatusIconContainer.java

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

Its implementation is a custom layout based on the AlphaOptimizedLinearLayout layout. AlphaOptimizedLinearLayout is inherited from LinearLayout but overridden

public boolean hasOverlappingRendering() {  
    return false;  
}

This method is used to mark whether the current view is overdrawn, true is returned, false is not, and true is returned by default. There is a transparency attribute in the android View. When setting the transparency setAlpha, android will draw the current view into the offscreen buffer by default, and then display it. This offscreen buffer can be understood as a temporary buffer, which puts the current View in and converts the transparency, and then displays it on the screen. This process is resource consuming, so it should be avoided as much as possible. And when inheriting the hasOverlappingRendering() method returns false, android will automatically perform reasonable optimization to avoid using offscreen buffer.

The system icon drawing process will be more. First analyze from the drawing of the top-level view StatusIconContainer. The drawing of view is inseparable from three steps, onMeasure, onLayout, onDraw, now let’s disassemble and check them one by one.

StatusIconContainer – onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 获取到所有需要展示的view,注意看不可见的,icon处于blocked状态的,
        // 还有需要忽略的都不会被加入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数量,默认为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) {
                    // 超过最大可见数量时 需要给省略点计算空间。
                    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));
        }
    }

As can be seen from the above, onMeaure mainly calculates the width and height of each child view, and calculates the entire width of the parent view, which will calculate the width of the omission point when the maximum number is exceeded. This omission can be determined according to the project situation. The number of points, which can be customized by constants in the code.

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的状态。通过StatusIconState重置状态
        resetViewStates();
        // 重新依据实际情况计算每个icon的显示状态,下面单独拎出来讲。
        calculateIconTranslations();
        // 应用view的状态,包含icon显示的动画。
        applyIconStates();
    }

The onLayout routine is to calculate the width and height of each view, arrange them according to predetermined rules, and then calculate the position of each view. calculateIconTranslations display logic will be more, take it out separately:

    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 下雨
            // 小于显示的内容就停止,后续就从这个index开始绘制
            if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
                    (mShouldRestrictIcons && visible >= maxVisible)) {
                firstUnderflowIndex = i;
                break;
            }
            mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
            visible++;
        }

        //后续逻辑就是配置是否显示icon和显示多少个dot
        ...
    }

onLayout has more logic. Simply put, it calculates how many icons need to be displayed through the xTranslation of each subview and the overall view space, and reserves space for omission points. A simple indication is as follows. If it may exceed the space, use dot to display it.

StatusIconContainer – onDraw

There is no custom processing for this piece, but some information drawing of debug is done.

At this point, the analysis of the top-level view of the system icon is completed. It mainly determines whether to display which icons need to be displayed based on the state of the child view and the space of the parent view, and displays the ellipsis symbol. Next, look at the situation of each child view.

The child view is to display the icon on the status bar, but it encapsulates a layer of ImageView with animation effects inherited from AnimatedImageView. The specific implementation of the child view StatusBarIconView

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

Pick one of the key points to analyze. Image scaling, how to fit the size of any image to be displayed in the status bar.

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

First get the scaling ratio of mIconScale, mSystemIconDesiredHeight is the configured global system icon size.

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

In the case of an icon, mIconScale is calculated by obtaining the actual size of the icon.

During onDraw, use canvas.scale to scale the canvas to the center point of the icon according to mIconScale to system_icon_size. But there is a problem, the actual size of the icon is still the original size, but the display is smaller. Other parts include animation, so I won't go into details.

    @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();
        }
        ...
    }

At this point, the general analysis of the status bar layout and the view drawing of the system icon is completed. Next, let's see how the icon controls adding, deleting and updating.

Status bar icons show logic controls

The status bar icon display logic is managed by the StatusBarIconControllerImpl class, which is initialized by default when the object is constructed

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

config_statusBarIcons This array contains all supported icons. If you need to customize the icon order, you can adjust the item corresponding to the icon in this list.

   <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>

The icon string information is regarded as a title information and saved in the mSlots list. And Slot contains 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;
        ...
}

Starting mIcon is the saving class for the displayed icon resource. It contains icon display status, label information and status information, etc. Save them in the mSlogs list for easy management and display.

Summarize the data storage chain Slots–> StatusBarIconHolder --> StatusBarIcon;

key view management

When the status bar is initialized, onViewCreated in the view creation of CollapsedStatusBarFragment initializes the system icon management.

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

The DarkIconManager constructor passes in the system_icon container viewgroup, which is responsible for adding and deleting views. StatusBarIconController manages DarkIconManager. In this way, the display icon area control part is associated with the display part.

How are the icons updated?

The implementation policy classes of control management are implemented in PhoneStatusBarPolicy. The specific implementation is realized through the StatusBarIconControllerImpl class, and the display icon can be updated through the following interface.

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

Take the volume update as an example. setIcon process analysis.

    @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创建一个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);
        }
    }

The slot is the title of the icon, the resourceId is the resource file, and the contentDescription is the description string. If it is judged that there is no holder, a new holder class will be created and passed into the list of 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 is implemented in DarkIconManager.

        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 即为状态栏系统图标的容器view。这里就完成了view的添加
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
        }

In this way, the icon is added to the system icon area through setIcon, and then the icon is displayed through setIconVisibility. The displayed logic is similar to that of setIcon, but the visible state is added, which can be analyzed by itself. During the update process, there are two special icons, wifi and data network, the status of which will contain multiple, normally there is only display logic, so there will be more logic here, but the principle is the same.

So far, the icon display control analysis of the whole system has been completed.

How to customize?

  1. The quantity and order can add or delete the icons you need by configuring config_statusBarIcons.

  2. Status bar size customization framework/base/core/res/res/values/dimens.xml

    configuration item illustrate
    status_bar_height_portrait status bar height
    status_bar_system_icon_intrinsic_size The expected size of the system icon is used for scaling the icon, which can be the same size as the icon_size setting
    status_bar_system_icon_size System icon size

    SystemUI/res/values/dimens.xml

    configuration item illustrate
    status_bar_padding_start The status bar is separated from the left space
    status_bar_padding_end Space from the status bar to the right
    signal_cluster_battery_padding The distance between the system icon and the battery icon
  3. Icon display customization Find the corresponding icon through the above analysis code setIcon to replace the icon of your own project. In the StatusIconContainer, you need to modify MAX_DOTS to 0, and do not display ellipsis points. In onLayout, you need to set the icon spacing according to the project, and increase the r value in child.layout.

  4. The display logic strategy is implemented in PhoneStatusBarPhicy, especially the icon logic that is not natively supported by the system will be more customized.

Precautions:

  1. For icon selection, when using a new svg icon, it is best to set the width and height to be consistent with the system_icon_size. The native logic will scale the icon to the same height as the system_icon, but the width remains the original icon width, resulting in an incorrect display layout.

---------------------
Author: bugyinyin
Source: CSDN
Original: https://blog.csdn.net/buyinyin/article/details/127531195
Copyright statement: This article Original article for the author, please attach the blog post link for reprint!
Content analysis By: CSDN, CNBLOG blog post one-click reprint plug-in

Guess you like

Origin blog.csdn.net/xiaowang_lj/article/details/130897983