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、资源文件
-
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>
-
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" />
-
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、缺陷
- 电池图标不会随系统分辨率切换而切换
- 纯代码绘制,无法进行更加精准的定制类型