SystemUI修改状态栏电池图标样式为横屏显示(以Android V为例)

news2024/12/27 7:34:16

SystemUI修改状态栏电池图标样式为横屏显示(以Android V为例)

1、概述

在15.0的系统rom产品定制化开发中,对于原生系统中SystemUId 状态栏的电池图标是竖着显示的,一般手机的电池图标都是横屏显示的
可以觉得样式挺不错的,所以由于产品开发要求电池图标横着显示和手机的样式一样,所以就得重新更换SystemUI状态栏的电池样式了

如图:

在这里插入图片描述

2、SystemUI修改状态栏电池图标样式为横屏显示的核心类(以MTK代码做修改)

vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java

vendor/mediatek/proprietary/packages/apps/SystemUI/res/layout/status_bar.xml

vendor/mediatek/proprietary/packages/apps/SystemUI/res/system_icons.xml

3.SystemUI修改状态栏电池图标样式为横屏显示的核心功能分析

systemui其实结构是比较复杂,里面管理各种服务,导航栏,状态栏,近期列表,下拉菜单,关机界面等,
其中以导航栏和状态栏,近期列表用的比较多,
从结构上来讲下拉菜单和状态栏都是属于statusbar,结构树上也是属于顶层的
status_bar.xml(StatusBarWindowView)的,
在SystemUI中状态栏的布局就是status_bar.xml,
接下来看SystemUI中的电池布局 status_bar.xml 中

3.1status_bar.xml 相关布局分析

在SystemUI修改状态栏电池图标样式为横屏显示的核心功能实现中,通过上述的分析,得知在系统systemui中关于状态栏的布局文件就是status_bar.xml,接下来分析下相关的源码实现

在这里插入图片描述

继续看下 system_icons.xml 中的

在这里插入图片描述

在SystemUI修改状态栏电池图标样式为横屏显示的核心功能实现中,通过上述的分析,得知在系统systemui
中status_bar.xml中关于状态栏图标的布局system_icons.xml中就是关于图标的布局,
在system_icons.xml中可以看出 com.android.systemui.BatteryMeterView 即为电池图标
接下来看下BatteryMeterView的相关源码分析

3.2、BatteryMeterView的相关源码分析

在SystemUI修改状态栏电池图标样式为横屏显示的核心功能实现中,通过上述的分析,得知在系统systemui
中的上述分析得知,在电池的相关核心类中就是BatteryMeterView.java来负责电池图标的绘制,所以接下来
需要分析下BatteryMeterView.java的相关电池图标的绘制,
具体BatteryMeterView的相关源码分析如下:

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.battery;

import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;

....

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.text.NumberFormat;
import java.util.ArrayList;

public class BatteryMeterView extends LinearLayout implements DarkReceiver {

    @Retention(SOURCE)
    @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
    public @interface BatteryPercentMode {}
    ....
        
    private TextView mBatteryPercentView;
    //add code start {@
    private BatteryView mCrossBatteryView;
    private ImageView mElectricityView;
    //add code end @}

    private final @StyleRes int mPercentageStyleId;
    private int mTextColor;
    private int mLevel;
    private int mShowPercentMode = MODE_DEFAULT;
    private boolean mShowPercentAvailable;
    private String mEstimateText = null;
    private boolean mPluggedIn;
    private boolean mPowerSaveEnabled;
    private boolean mIsBatteryDefender;
    private boolean mIsIncompatibleCharging;
    private boolean mDisplayShieldEnabled;
    // Error state where we know nothing about the current battery state
    private boolean mBatteryStateUnknown;
    // Lazily-loaded since this is expected to be a rare-if-ever state
    private Drawable mUnknownStateDrawable;

    private DualToneHandler mDualToneHandler;
    private boolean mIsStaticColor = false;

    private BatteryEstimateFetcher mBatteryEstimateFetcher;

    // for Flags.newStatusBarIcons. The unified battery icon can show percent inside
    @Nullable private BatteryLayersDrawable mUnifiedBattery;
    private BatteryColors mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
    private BatteryDrawableState mUnifiedBatteryState =
            BatteryDrawableState.Companion.getDefaultInitialState();

    // add for LOTX
    private boolean isOverchargedActive;
    private Drawable mOverchargedDrawable;
    private ImageView mOverchargedIcon;

    public BatteryMeterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL | Gravity.START);

        TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
                defStyle, 0);
        final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                context.getColor(com.android.settingslib.R.color.meter_background_color));
        mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);

        mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
        atts.recycle();

        mShowPercentAvailable = context.getResources().getBoolean(
                com.android.internal.R.bool.config_battery_percentage_setting_available);

        setupLayoutTransition();

        mBatteryIconView = new ImageView(context);
        if (newStatusBarIcons()) {
            mUnifiedBattery = BatteryLayersDrawable.Companion
                    .newBatteryDrawable(context, mUnifiedBatteryState);
            mBatteryIconView.setImageDrawable(mUnifiedBattery);

            final MarginLayoutParams mlp = new MarginLayoutParams(
                    getResources().getDimensionPixelSize(
                            R.dimen.status_bar_battery_unified_icon_width),
                    getResources().getDimensionPixelSize(
                            R.dimen.status_bar_battery_unified_icon_height));
            addView(mBatteryIconView, mlp);
        } else {
            mBatteryIconView.setImageDrawable(mDrawable);
            final MarginLayoutParams mlp = new MarginLayoutParams(
                    getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
                    getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
            mlp.setMargins(0, 0, 0,
                    getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
            addView(mBatteryIconView, mlp);

        }
        // add for LOTX
        addOverchargedIconView();

        //add code start {@
        // 添加电池图标
        mBatteryIconView.setVisibility(View.GONE);
        mCrossBatteryView = (BatteryView) LayoutInflater.from(getContext()).inflate(R.layout.cross_battery_view, null);
        addView(mCrossBatteryView, new ViewGroup.LayoutParams(58 , 25));

        //添加电池充电图标
        mElectricityView = (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.cross_battery_electricity_view, null);
        addView(mElectricityView, new ViewGroup.LayoutParams(20, 22));
        //add code end @}

        updateShowPercent();
        mDualToneHandler = new DualToneHandler(context);
        // Init to not dark at all.
        onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);

        setClipChildren(false);
        setClipToPadding(false);
    }


    private void setBatteryDrawableState(BatteryDrawableState newState) {
        if (!newStatusBarIcons()) return;

        mUnifiedBatteryState = newState;
        mUnifiedBattery.setBatteryState(mUnifiedBatteryState);
    }

    private void setupLayoutTransition() {
        LayoutTransition transition = new LayoutTransition();
        transition.setDuration(200);

        // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
        // and disables all other animation types
        ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
        transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
        transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);

        ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
        transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
        transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);

        transition.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
        transition.setAnimator(LayoutTransition.CHANGING, null);

        setLayoutTransition(transition);
    }

    public void setForceShowPercent(boolean show) {
        setPercentShowMode(show ? MODE_ON : MODE_DEFAULT);
    }

    /**
     * Force a particular mode of showing percent
     *
     * 0 - No preference
     * 1 - Force on
     * 2 - Force off
     * 3 - Estimate
     * @param mode desired mode (none, on, off)
     */
    public void setPercentShowMode(@BatteryPercentMode int mode) {
        if (mode == mShowPercentMode) return;
        mShowPercentMode = mode;
        updateShowPercent();
        updatePercentText();
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        updatePercentView();
        mDrawable.notifyDensityChanged();
    }

    public void setColorsFromContext(Context context) {
        if (context == null) {
            return;
        }

        mDualToneHandler.setColorsFromContext(context);
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }

    /**
     * Update battery level
     *
     * @param level     int between 0 and 100 (representing percentage value)
     * @param pluggedIn whether the device is plugged in or not
     */
    public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
        boolean wasCharging = isCharging();
        mPluggedIn = pluggedIn;
        mLevel = level;
        boolean isCharging = isCharging();
        mDrawable.setCharging(isCharging);
        mDrawable.setBatteryLevel(level);
        updatePercentText();

        //add code start {@
        mCrossBatteryView.setProgress(mLevel);  //设置电池图标内电量值
        //设置充电图标
        if (wasCharging && (level != 100)) {
            mElectricityView.setVisibility(View.VISIBLE);
        } else {
            mElectricityView.setVisibility(View.GONE);
        }
        //add code end @}

        if (newStatusBarIcons()) {
            Drawable attr = mUnifiedBatteryState.getAttribution();
            if (isCharging != wasCharging) {
                attr = getBatteryAttribution(isCharging);
            }

            BatteryDrawableState newState =
                    new BatteryDrawableState(
                            level,
                            mUnifiedBatteryState.getShowPercent(),
                            getCurrentColorProfile(),
                            attr
                    );

            setBatteryDrawableState(newState);
        }
    }

    // Potentially reloads any attribution. Should not be called if the state hasn't changed
    @SuppressLint("UseCompatLoadingForDrawables")
    private Drawable getBatteryAttribution(boolean isCharging) {
        if (!newStatusBarIcons()) return null;

        int resId = 0;
        if (mPowerSaveEnabled) {
            resId = R.drawable.battery_unified_attr_powersave;
        } else if (mIsBatteryDefender && mDisplayShieldEnabled) {
            resId = R.drawable.battery_unified_attr_defend;
        } else if (isCharging) {
            resId = R.drawable.battery_unified_attr_charging;
        }

        Drawable attr = null;
        if (resId > 0) {
            attr = mContext.getDrawable(resId);
        }

        return attr;
    }

    /** Calculate the appropriate color for the current state */
    private ColorProfile getCurrentColorProfile() {
        return getColorProfile(
                mPowerSaveEnabled,
                mIsBatteryDefender && mDisplayShieldEnabled,
                mPluggedIn,
                mLevel <= 20);
    }

    /** pure function to compute the correct color profile for our battery icon */
    private ColorProfile getColorProfile(
            boolean isPowerSave,
            boolean isBatteryDefender,
            boolean isCharging,
            boolean isLowBattery
    ) {
        if (isCharging)  return ColorProfile.Active;
        if (isPowerSave) return ColorProfile.Warning;
        if (isBatteryDefender) return ColorProfile.None;
        if (isLowBattery) return ColorProfile.Error;

        return ColorProfile.None;
    }

    void onPowerSaveChanged(boolean isPowerSave) {
        if (isPowerSave == mPowerSaveEnabled) {
            return;
        }
        mPowerSaveEnabled = isPowerSave;
        if (!newStatusBarIcons()) {
            mDrawable.setPowerSaveEnabled(isPowerSave);
        } else {
            setBatteryDrawableState(
                    new BatteryDrawableState(
                            mUnifiedBatteryState.getLevel(),
                            mUnifiedBatteryState.getShowPercent(),
                            getCurrentColorProfile(),
                            getBatteryAttribution(isCharging())
                    )
            );
        }
    }

    void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
        boolean valueChanged = mIsBatteryDefender != isBatteryDefender;
        mIsBatteryDefender = isBatteryDefender;

        if (!valueChanged) {
            return;
        }

        updateContentDescription();
        if (!newStatusBarIcons()) {
            // The battery drawable is a different size depending on whether it's currently
            // overheated or not, so we need to re-scale the view when overheated changes.
            scaleBatteryMeterViews();
        } else {
            setBatteryDrawableState(
                    new BatteryDrawableState(
                            mUnifiedBatteryState.getLevel(),
                            mUnifiedBatteryState.getShowPercent(),
                            getCurrentColorProfile(),
                            getBatteryAttribution(isCharging())
                    )
            );
        }
    }

    void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
        boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
        mIsIncompatibleCharging = isIncompatibleCharging;
        if (valueChanged) {
            if (newStatusBarIcons()) {
                setBatteryDrawableState(
                        new BatteryDrawableState(
                                mUnifiedBatteryState.getLevel(),
                                mUnifiedBatteryState.getShowPercent(),
                                getCurrentColorProfile(),
                                getBatteryAttribution(isCharging())
                        )
                );
            } else {
                mDrawable.setCharging(isCharging());
            }
            updateContentDescription();
        }
    }

    private TextView inflatePercentView() {
        return (TextView) LayoutInflater.from(getContext())
                .inflate(R.layout.battery_percentage_view, null);
    }

    private void addPercentView(TextView inflatedPercentView) {
        mBatteryPercentView = inflatedPercentView;
        //add code start {@
        //屏蔽原有电池电量显示
        mBatteryPercentView.setVisibility(View.GONE);
        //add code end @}

        if (mPercentageStyleId != 0) { // Only set if specified as attribute
            mBatteryPercentView.setTextAppearance(mPercentageStyleId);
        }
        float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
        mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
        if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
        addView(mBatteryPercentView, new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                (int) Math.ceil(fontHeight)));
    }

    /**
     * Updates percent view by removing old one and reinflating if necessary
     */
    public void updatePercentView() {
        if (mBatteryPercentView != null) {
            removeView(mBatteryPercentView);
            mBatteryPercentView = null;
        }
        updateShowPercent();
    }

    /**
     * Sets the fetcher that should be used to get the estimated time remaining for the user's
     * battery.
     */
    void setBatteryEstimateFetcher(BatteryEstimateFetcher fetcher) {
        mBatteryEstimateFetcher = fetcher;
    }

    void setDisplayShieldEnabled(boolean displayShieldEnabled) {
        mDisplayShieldEnabled = displayShieldEnabled;
    }

    void updatePercentText() {
        if (!newStatusBarIcons()) {
            updatePercentTextLegacy();
            return;
        }

        // The unified battery can show the percent inside, so we only need to handle
        // the estimated time remaining case
        if (mShowPercentMode == MODE_ESTIMATE
                && mBatteryEstimateFetcher != null
                && !isCharging()
        ) {
            mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                    (String estimate) -> {
                        if (mBatteryPercentView == null) {
                            // Similar to the legacy behavior, inflate and add the view. We will
                            // only use it for the estimate text
                            addPercentView(inflatePercentView());
                        }
                        if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
                            mEstimateText = estimate;
                            mBatteryPercentView.setText(estimate);
                            updateContentDescription();
                        } else {
                            mEstimateText = null;
                            mBatteryPercentView.setText(null);
                            updateContentDescription();
                        }
                    });
        } else {
            if (mBatteryPercentView != null) {
                mEstimateText = null;
                mBatteryPercentView.setText(null);
            }
            updateContentDescription();
        }
    }

    void updatePercentTextLegacy() {
        if (mBatteryStateUnknown) {
            return;
        }

        if (mBatteryEstimateFetcher == null) {
            setPercentTextAtCurrentLevel();
            return;
        }

        if (mBatteryPercentView != null) {
            if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) {
                mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                        (String estimate) -> {
                    if (mBatteryPercentView == null) {
                        return;
                    }
                    if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
                        mEstimateText = estimate;
                        mBatteryPercentView.setText(estimate);
                        updateContentDescription();
                    } else {
                        setPercentTextAtCurrentLevel();
                    }
                });
            } else {
                setPercentTextAtCurrentLevel();
            }
        } else {
            updateContentDescription();
        }
    }

    private void setPercentTextAtCurrentLevel() {
        if (mBatteryPercentView != null) {
            mEstimateText = null;
            String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
            // Setting text actually triggers a layout pass (because the text view is set to
            // wrap_content width and TextView always relayouts for this). Avoid needless
            // relayout if the text didn't actually change.
            if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
                mBatteryPercentView.setText(percentText);
            }
        }

        updateContentDescription();
    }

    private void updateContentDescription() {
        Context context = getContext();

        String contentDescription;
        if (mBatteryStateUnknown) {
            contentDescription = context.getString(R.string.accessibility_battery_unknown);
        } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
            contentDescription = context.getString(
                    mIsBatteryDefender
                            ? R.string.accessibility_battery_level_charging_paused_with_estimate
                            : R.string.accessibility_battery_level_with_estimate,
                    mLevel,
                    mEstimateText);
        } else if (mIsBatteryDefender) {
            contentDescription =
                    context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
        } else if (isCharging()) {
            contentDescription =
                    context.getString(R.string.accessibility_battery_level_charging, mLevel);
        } else {
            contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
        }

        setContentDescription(contentDescription);
    }

    void updateShowPercent() {
        if (!newStatusBarIcons()) {
            updateShowPercentLegacy();
            return;
        }

        if (!mShowPercentAvailable || mUnifiedBattery == null) return;

        boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
        if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
            // Slow case: fall back to the system setting
            // TODO(b/140051051)
            shouldShow = 0 != whitelistIpcs(() -> Settings.System
                    .getIntForUser(getContext().getContentResolver(),
                    SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
                    ? 1 : 0, UserHandle.USER_CURRENT));
        }

        setBatteryDrawableState(
                new BatteryDrawableState(
                        mUnifiedBatteryState.getLevel(),
                        shouldShow,
                        mUnifiedBatteryState.getColor(),
                        mUnifiedBatteryState.getAttribution()
                )
        );

        // The legacy impl used the percent view for the estimate and the percent text. The modern
        // version only uses it for estimate. It can be safely removed here
        if (mShowPercentMode != MODE_ESTIMATE) {
            removeView(mBatteryPercentView);
            mBatteryPercentView = null;
        }
    }

    private void updateShowPercentLegacy() {
        final boolean showing = mBatteryPercentView != null;
        // TODO(b/140051051)
        final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
                .getIntForUser(getContext().getContentResolver(),
                SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
                ? 1 : 0, UserHandle.USER_CURRENT));
        boolean shouldShow =
                (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
                || mShowPercentMode == MODE_ON
                || mShowPercentMode == MODE_ESTIMATE;
        shouldShow = shouldShow && !mBatteryStateUnknown;

        if (shouldShow) {
            //add code start {@
            mCrossBatteryView.setShowBatteryText(true, mLevel);
            //add code end @}
            if (!showing) {
                addPercentView(inflatePercentView());
                updatePercentText();
            }
        } else {
            //add code start {@
            mCrossBatteryView.setShowBatteryText(false, mLevel);
            //add code end @}
            if (showing) {
                removeView(mBatteryPercentView);
                mBatteryPercentView = null;
            }
        }
    }

    private Drawable getUnknownStateDrawable() {
        if (mUnknownStateDrawable == null) {
            mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown);
            mUnknownStateDrawable.setTint(mTextColor);
        }

        return mUnknownStateDrawable;
    }

    void onBatteryUnknownStateChanged(boolean isUnknown) {
        if (mBatteryStateUnknown == isUnknown) {
            return;
        }

        mBatteryStateUnknown = isUnknown;
        updateContentDescription();

        if (mBatteryStateUnknown) {
            mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
        } else {
            mBatteryIconView.setImageDrawable(mDrawable);
        }

        // add for LOTX
        updateBatteryResource();

        updateShowPercent();
    }

    void scaleBatteryMeterViews() {
        if (!newStatusBarIcons()) {
            scaleBatteryMeterViewsLegacy();
            return;
        }

        // For simplicity's sake, copy the general pattern in the legacy method and use the new
        // resources, excluding what we don't need
        Resources res = getContext().getResources();
        TypedValue typedValue = new TypedValue();

        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
        float iconScaleFactor = typedValue.getFloat();

        float mainBatteryHeight =
                res.getDimensionPixelSize(
                        R.dimen.status_bar_battery_unified_icon_height) * iconScaleFactor;
        float mainBatteryWidth =
                res.getDimensionPixelSize(
                        R.dimen.status_bar_battery_unified_icon_width) * iconScaleFactor;

        LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
                Math.round(mainBatteryWidth),
                Math.round(mainBatteryHeight));

        mBatteryIconView.setLayoutParams(scaledLayoutParams);
        mBatteryIconView.invalidateDrawable(mUnifiedBattery);
    }

    /**
     * Looks up the scale factor for status bar icons and scales the battery view by that amount.
     */
    void scaleBatteryMeterViewsLegacy() {
        Resources res = getContext().getResources();
        TypedValue typedValue = new TypedValue();

        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
        float iconScaleFactor = typedValue.getFloat();

        float mainBatteryHeight =
                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
        float mainBatteryWidth =
                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;

        boolean displayShield = mDisplayShieldEnabled && mIsBatteryDefender;
        float fullBatteryIconHeight =
                BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
        float fullBatteryIconWidth =
                BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);

        int marginTop;
        if (displayShield) {
            // If the shield is displayed, we need some extra marginTop so that the bottom of the
            // main icon is still aligned with the bottom of all the other system icons.
            int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
            // However, the other system icons have some embedded bottom padding that the battery
            // doesn't have, so we shouldn't move the battery icon down by the full amount.
            // See b/258672854.
            marginTop = shieldHeightAddition
                    - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
        } else {
            marginTop = 0;
        }

        int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);

        LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
                Math.round(fullBatteryIconWidth),
                Math.round(fullBatteryIconHeight));
        scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);

        mDrawable.setDisplayShield(displayShield);
        mBatteryIconView.setLayoutParams(scaledLayoutParams);
        mBatteryIconView.invalidateDrawable(mDrawable);
    }

    @Override
    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
        if (mIsStaticColor) return;

        if (!newStatusBarIcons()) {
            onDarkChangedLegacy(areas, darkIntensity, tint);
            return;
        }

        if (mUnifiedBattery == null) {
            return;
        }

        if (DarkIconDispatcher.isInAreas(areas, this)) {
            if (darkIntensity < 0.5) {
                mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
            } else {
                mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
            }

            mUnifiedBattery.setColors(mUnifiedBatteryColors);
        } else  {
            // Same behavior as the legacy code when not isInArea
            mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
            mUnifiedBattery.setColors(mUnifiedBatteryColors);
        }
    }

    private void onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint) {
        float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0;
        int nonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);
        int nonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
        int nonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);

        updateColors(nonAdaptedForegroundColor, nonAdaptedBackgroundColor,
                nonAdaptedSingleToneColor);
    }

    // add for LOTX
    private void addOverchargedIconView() {
        mOverchargedIcon = new ImageView(mContext);
        mOverchargedIcon.setImageDrawable(getOverchargedDrawable());
        final MarginLayoutParams mlp = new MarginLayoutParams(
                getResources().getDimensionPixelSize(R.dimen.status_bar_overcharged_width),
                getResources().getDimensionPixelSize(R.dimen.status_bar_overcharged_height));
        mlp.setMargins(0, 0, 0,
                getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
        addView(mOverchargedIcon, mlp);
        mOverchargedIcon.setVisibility(View.GONE);
    }

    private Drawable getOverchargedDrawable() {
        if (mOverchargedDrawable == null) {
            mOverchargedDrawable = mContext.getDrawable(R.drawable.ic_smart_battery);
            mOverchargedDrawable.setTint(mTextColor);
        }
        return mOverchargedDrawable;
    }

    private void updateBatteryResource() {
        mBatteryIconView.setVisibility((isOverchargedActive && !mBatteryStateUnknown) ? GONE : VISIBLE);
        mOverchargedIcon.setVisibility((isOverchargedActive && !mBatteryStateUnknown) ? VISIBLE : GONE);
    }

    public void updateOverchargedActive(boolean active) {
        if (active != isOverchargedActive) {
            isOverchargedActive = active;
            updateBatteryResource();
        }
    }

    public void setStaticColor(boolean isStaticColor) {
        mIsStaticColor = isStaticColor;
    }

    /**
     * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
     * if registered.
     *
     * @param foregroundColor
     * @param backgroundColor
     * @param singleToneColor
     */
    public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
        mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
        mTextColor = singleToneColor;
        if (mBatteryPercentView != null) {
            mBatteryPercentView.setTextColor(singleToneColor);
        }

        //add code start {@
        //设置电池图标颜色
        if (mCrossBatteryView != null) {
            mCrossBatteryView.setColor(singleToneColor);
        }
        //设置电池充电图标颜色
        if (mElectricityView != null) {
            mElectricityView.setColorFilter(singleToneColor);
        }
        //add code end @}

        if (mUnknownStateDrawable != null) {
            mUnknownStateDrawable.setTint(singleToneColor);
        }

        // add for LOTX
        if (mOverchargedDrawable != null) {
            mOverchargedDrawable.setTint(singleToneColor);
        }
    }

    ....
}

3.3、添加自定义电池电量图标的View来重新定制电池横着充电样式

在SystemUI修改状态栏电池图标样式为横屏显示的核心功能实现中,通过上述的分析,得知在系统systemui
中的上述分析得知,去掉了原来的systemui的充电电池图标样式,接下来就需要重新定义关于电池电量充电
的样式来满足横着充电的样式,具体如下:

package com.android.systemui.battery;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import com.android.systemui.res.R;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

public class BatteryView extends View {
	private float percent = 0f;	//电量百分比
	private boolean showBatteryText = true;	//控制是否显示电量文本
	Paint paint = new Paint();
	Paint paint1 = new Paint();
	Paint paint2 = new Paint();

	public BatteryView(Context context, AttributeSet set) {
		super(context, set);
		paint.setAntiAlias(true);
		paint.setStyle(Paint.Style.FILL);
		paint.setColor(Color.GRAY);
		paint1.setAntiAlias(true);
		paint1.setStyle(Paint.Style.STROKE);
		paint1.setStrokeWidth(dip2px(1.5f));
		paint1.setColor(-1728053248);
		paint2.setAntiAlias(true);
		paint2.setStyle(Paint.Style.FILL);
		paint2.setColor(-1728053248);

		DisplayMetrics dm = getResources().getDisplayMetrics();
		int mScreenWidth = dm.widthPixels;
		int mScreenHeight = dm.heightPixels;

		float ratioWidth = (float) mScreenWidth / 720;
		float ratioHeight = (float) mScreenHeight / 1080;
		float ratioMetrics = Math.min(ratioWidth, ratioHeight);
		int textSize = Math.round(20 * ratioMetrics);
		paint2.setTextSize(textSize);
	}

	private int dip2px(float dpValue) {
		final float scale = getResources().getDisplayMetrics().density;
		return (int) (dpValue * scale + 0.5f);
	}

	@Override
	protected void onDraw(@NonNull Canvas canvas) {
		super.onDraw(canvas);

		//绘制电池进度条
		int a = getWidth() - dip2px(2f);	//电池的宽度
		int b = getHeight() - dip2px(1.5f);	//电池的高度
		float d = a * percent;	//进度条宽度,按百分比

		//设置圆角半径
		float cornerRadius = dip2px(4f);

		//绘制进度条矩形区域
		float left = dip2px(0.5f);
		float top = dip2px(0.5f);
		float right = dip2px(2.5f);
		float bottom = dip2px(1.5f);

		//绘制进度条背景、边框和电池电量区
		RectF re1 = new RectF(left, top, d - right, b + bottom);	//进度条
		RectF re2 = new RectF(0, 0, a - right, b + bottom);	//边框
		RectF re3 = new RectF(a - right + 2, b / 5, a, b + bottom - b / 5);	//电池电量区

//		canvas.drawRect(re1, paint);	//绘制进度条
//		canvas.drawRect(re2, paint1);	//绘制边框
//		canvas.drawRect(re3, paint1);	//绘制电池电量区
		canvas.drawRoundRect(re1, cornerRadius, cornerRadius, paint);
		canvas.drawRoundRect(re2, cornerRadius, cornerRadius , paint1);	//绘制边框(带圆角)
		canvas.drawRoundRect(re3, cornerRadius, cornerRadius, paint1);

		if (showBatteryText) {
			// 计算电池百分比文字
			String text = String.valueOf((int) (percent * 100));	//电池百分比字符串
			Rect textBounds = new Rect();	//用于测量文本大小
			paint2.getTextBounds(text, 0, text.length(), textBounds);	//测量文本的宽度

			float textWidth = textBounds.width();	//获取文本的宽度
			float textHeight = textBounds.height();	// 获取文本的高度

			// 根据可用空间调整字体大小
			float availableWidth = a - dip2px(4f);	//可用的宽度(边距调整)
			float availableHeight = b - dip2px(2f);	//可用的高度(边距调整)

			//计算文本适合的缩放比例
			float scale = Math.min(availableWidth / textWidth, availableHeight / textHeight); //选择最小比例
			float newTextSize = paint2.getTextSize() * scale;	//计算新的字体
			paint2.setTextSize(newTextSize);	//设置新的字体大小

			//计算文本的绘制位置
			float x = getWidth() / 4 - dip2px(2);	//x轴坐标
			float y = getHeight() - getHeight() / 5; //y轴坐标

			//绘制文本
			canvas.drawText(text, x, y, paint2);

			//打印日志
			android.util.Log.d("BatteryView", "onDraw: getWidth : " + getWidth() + " getHeight: " + getHeight() +
					" textSize: " + newTextSize + " textWidth: " + textWidth + " textHeight: " + textHeight + " x: " + x + " y: " + y);
		}
	}

	// 设置是否显示电池百分比
	public synchronized void setShowBatteryText(boolean show, int percent) {
		this.showBatteryText = show;
		setProgress(percent);
		postInvalidate();
	}

	public synchronized void setProgress(int percent) {
		this.percent = (float) (percent / 100.0);
		postInvalidate();
	}

	public void setColor(int color) {
		paint1.setColor(color);
		paint2.setColor(color);
		postInvalidate();
	}
}

3.4、资源文件

  1. ic_electricity.xml

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="16dp"
        android:height="16dp"
        android:viewportWidth="1024"
        android:viewportHeight="1024">
        <path
            android:pathData="M788.48,410.11h-112.13c-24.06,0 -92.16,11.78 -115.2,-2.56 -4.61,-16.38 10.24,-46.08 14.34,-60.42 11.78,-41.47 23.04,-82.43 34.82,-124.42L660.48,41.98c6.66,-23.55 -24.06,-33.79 -36.86,-15.87L218.11,582.66c-9.22,12.8 3.58,30.72 17.92,30.72h164.35c11.26,0 54.78,-7.17 61.95,3.58 6.66,8.7 -6.14,35.84 -8.7,45.57 -30.72,106.5 -60.42,212.99 -90.11,319.49 -6.66,23.55 24.06,33.79 36.86,15.87l404.99,-556.54c9.73,-13.31 -2.56,-31.23 -16.9,-31.23z"
            android:fillColor="#FAF6F6"/>
    </vector>
    
    
  2. cross_battery_electricity_view.xml.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      ~ Copyright (C) 2017 The Android Open Source Project
      ~
      ~ Licensed under the Apache License, Version 2.0 (the "License");
      ~ you may not use this file except in compliance with the License.
      ~ You may obtain a copy of the License at
      ~
      ~      http://www.apache.org/licenses/LICENSE-2.0
      ~
      ~ Unless required by applicable law or agreed to in writing, software
      ~ distributed under the License is distributed on an "AS IS" BASIS,
      ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      ~ See the License for the specific language governing permissions and
      ~ limitations under the License
      -->
    
    <!-- Loaded into BatteryMeterView as necessary -->
    <ImageView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/cross_battery_electricity_view"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:src="@drawable/ic_electricity"
        />
    
    
    
  3. cross_battery_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      ~ Copyright (C) 2017 The Android Open Source Project
      ~
      ~ Licensed under the Apache License, Version 2.0 (the "License");
      ~ you may not use this file except in compliance with the License.
      ~ You may obtain a copy of the License at
      ~
      ~      http://www.apache.org/licenses/LICENSE-2.0
      ~
      ~ Unless required by applicable law or agreed to in writing, software
      ~ distributed under the License is distributed on an "AS IS" BASIS,
      ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      ~ See the License for the specific language governing permissions and
      ~ limitations under the License
      -->
    
    <!-- Loaded into BatteryMeterView as necessary -->
    <com.android.systemui.battery.BatteryView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/cross_battery_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:gravity="center_vertical|start"
        />
    

    在SystemUI修改状态栏电池图标样式为横屏显示的核心功能实现中,通过上述的分析,得知在系统systemui
    中的上述分析得知,在重新定制电池充电图标的样式后,去掉了原来的系统电池充电电量显示图标,然后重新
    定制BatteryView以及相关的电池充电图标的样式和资源来作为新的电池电量充电图标样式来满足当前的功能

3、缺陷

  1. 电池图标不会随系统分辨率切换而切换
  2. 纯代码绘制,无法进行更加精准的定制类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2254147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最长最短单词

最长最短单词 C语言实现C实现Java实现Python实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入1行句子&#xff08;不多于200个单词&#xff0c;每个单词长度不超过100&#xff09;&#xff0c;只包含字母、空格和逗号。单词由至少一…

C++【PCL】点云数据平移 旋转,对点云进行刚体变化

//头文件 #include <iostream>#include <pcl/point_cloud.h>#include<pcl/io/pcd_io.h>#include <pcl/common/transforms.h> #include <pcl/io/ply_io.h>//主函数int main() {pcl::PointCloud<pcl::PointXYZ>::Ptr source_cloud(new pcl::…

2024第六届金盾信安杯Web 详细题解

比赛一共4道Web题,比赛时只做出三道,那道文件上传没有做出来,所以这里是另外三道题的WP 分别是 fillllll_put hoverfly ssrf fillllll_put 涉及: 绕过exit() 死亡函数 php://filter 伪协议配合base64加解密 一句话木马 题目源码&#xff1a; $content参数在开头被…

006 MATLAB编程基础

01 M文件 MATLAB输入命令有两种方法&#xff1a; 一是在MATLAB主窗口逐行输入命令&#xff0c;每个命令之间用分号或逗号分隔&#xff0c;每行可包含多个命令。 二是将命令组织成一个命令语句文集&#xff0c;使用扩展名“.m”&#xff0c;称为M文件。它由一系列的命令和语句…

Delphi Web前端开发教程(9):基于TMS WEB Core框架

3、REST Servers服务端(后端)框架 REST服务端特点&#xff1a; – 为远程资源提供一个REST API接口。也可以为其他网络内容提供服务&#xff1b; – 包括在Delphi Enterprise & Architect企业版和架构师版中的RAD服务器、DataSnap、WebBroker&#xff1b; – 开源框架&a…

SPC三种判定准则的算法

1.连续6个点递增或递减 //传入数据列表 //返回连续X个及以上递增或递减的数组下标int n = array.Length; int X = X_in; List<int> regions_start = new List<int>(); List<int> regions_end = new List<int>();if(Open){for (int i = 0; i < n - (…

AI一键生成原创圣诞印花图案

一、引言 随着科技的飞速发展&#xff0c;AI 已经深入到我们生活和工作的各个角落&#xff0c;为创意设计领域带来了前所未有的变革。在圣诞即将来临之际&#xff0c;想要设计独特的圣诞印花图案却又担心缺乏灵感或专业技能&#xff1f;别担心&#xff0c;千鹿 AI 为我们提供了…

Ubuntu的pip怎么用

第一步&#xff1a;查看python3版本 第二步&#xff1a;安装pip 第三步&#xff1a;可以尝试使用pip list查看 也可以尝试安装 下面这条命令可以设置永久源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

基于Matlab三点雨流计数法的载荷时间历程分析与循环疲劳评估

随着工程领域中机械设备和结构系统的复杂性不断增加&#xff0c;疲劳分析成为评估其可靠性与使用寿命的关键环节。载荷时间历程数据在疲劳分析中扮演着重要角色&#xff0c;而雨流计数法作为经典的循环计数方法&#xff0c;能够有效地从载荷时间历程中提取疲劳载荷循环信息。本…

帝可得-策略管理

策略管理 需求说明 策略管理主要涉及到二个功能模块&#xff0c;业务流程如下&#xff1a; 新增策略: 允许管理员定义新的策略&#xff0c;包括策略的具体内容和参数&#xff08;如折扣率&#xff09;策略分配: 将策略分配给一个或多个售货机。 #mermaid-svg-PSQOJMLJqVGn3W…

【数据结构】手搓链表

一、定义 typedef struct node_s {int _data;struct node_s *_next; } node_t;typedef struct list_s {node_t *_head;node_t *_tail; } list_t;节点结构体&#xff08;node_s&#xff09;&#xff1a; int _data;存储节点中的数据struct node_s *_next;&#xff1a;指向 node…

嵌入式 C 编程必备(7):const 关键字 —— 打造稳定的常量空间

目录 一、const关键字的基本含义与用法 1.1. 修饰基本数据类型 1.2. 修饰指针 1.3. 修饰数组 1.4. 修饰结构体 二、const关键字在嵌入式编程中的优势 2.1. 提升代码可读性 2.2. 增强代码安全性 2.3. 优化内存使用 2.4. 促进模块化设计 2.5. 支持静态分析和测试 三、…

Day28 买卖股票的最佳时机 跳跃游戏 跳跃游戏 II K 次取反后最大化的数组和

贪心算法 part02 122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 求最大利润 将每天的正利润加和 public int maxProfit(int[] prices) {int totalPrices 0;for(int i0;i<prices.length;i){if(i<prices.length-1&&prices[i1]>prices[…

【专题】计算机网络之运输层(传输层)

1. 运输层协议概述 1.1 进程之间的通信 (1) 运输层的作用 运输层提供进程间的逻辑通信。 运输层的屏蔽作用&#xff1a; 运输层向高层用户屏蔽了下面网络核心的细节&#xff08;如网络拓扑、所采用的路由选择协议等&#xff09;&#xff0c;使应用进程看见的就是好像在两个运…

Ansible 运维工具

安装 apt install ansible /etc/ansible/hosts , 指定密码或密钥访问分组机器 [k8s_masters] master0.c0.k8s.sb[k8s_nodes] node0.c0.k8s.sb node1.c0.k8s.sb[k8s:children] k8s_masters k8s_nodes[k8s_masters:vars] ansible_ssh_usersbadmin ansible_ssh_pass"***&q…

matlab 中的 bug

在matlab中绘图&#xff0c;设置 axe 的背景颜色 axes_in3.Color #00235B ;打印的时候 print(figure_handle1,-dpng,-r300,"merge_yt_ey") ;此时保存的图片无法识别背景颜色 原因在于 matlab 中的 InverseHardcopy 将 InvertHardcopy 设置成 off 则可以解决这个问…

利用Python爬虫快速获取淘宝/天猫SKU详细信息数据

引言 在当今的电商环境中&#xff0c;获取商品的详细信息对于市场分析和竞争策略至关重要。阿里作为中国最大的电商平台&#xff0c;拥有海量的商品数据。本文将介绍如何利用Python编写爬虫程序&#xff0c;快速获取商品的SKU详细信息数据&#xff0c;并提供相应的代码示例。 …

R语言机器学习论文(二):数据准备

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据一、数据描述二、数据预处理(一)修改元素名称(二)剔除无关变量(三)缺失值检查(四)重复值检查(五)异常值检查三、描述性统计(一)连续变量数据情…

基于图像变化检测的毁伤效果评估——学习笔记

前言 闲的无聊&#xff0c;看看论文。 基本评估步骤 第一步&#xff1a;图像预处理。通过图像配准、不同波段提取、图像校正、图像滤波等手段&#xff0c;统一图像格式&#xff08;文中统一为灰度图&#xff09;&#xff0c;得到待检测图像&#xff1b; 第二步&#xff1a;…

A1228 php+Mysql旅游供需平台的设计与实现 导游接单 旅游订单 旅游分享网站 thinkphp框架 源码 配置 文档 全套资料

旅游供需平台 1.项目描述2. 开发背景与意义3.项目功能4.界面展示5.源码获取 1.项目描述 随着社会经济的快速发展&#xff0c;生活水平的提高&#xff0c;人们对旅游的需求日益增强&#xff0c;因此&#xff0c;为给用户提供一个便利的查看导游信息&#xff0c;进行导游招募的平…