目录
一、概述
原生的电池图标是竖向,并且很小,不太美观。现客户需求状态栏的电池图标变为横放的。众所周知,状态栏的修改在SystemUI里。
效果图:
二、电池图标显示所在源码分析
状态栏布局文件是status_bar。路径:SystemUI\res\layout\status_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:id="@+id/status_bar"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
android:accessibilityPaneTitle="@string/status_bar"
>
<ImageView
android:id="@+id/notification_lights_out"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingBottom="2dip"
android:src="@drawable/ic_sysbar_lights_out_dot_small"
android:scaleType="center"
android:visibility="gone"
/>
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
android:orientation="horizontal"
android:gravity="center_vertical"
>
..省略部分代码..
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="horizontal"
android:gravity="center_vertical|end"
>
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
<ViewStub
android:id="@+id/emergency_cryptkeeper_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/emergency_cryptkeeper_text"
/>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
电池图标就在布局system_icons中,路径:SystemUI\res\layout\system_icons.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/system_icons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<!--电池图标-->
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
</LinearLayout>
其中BatteryMeterView是自定义View,继承于LinearLayout,实现的接口有BatteryStateChangeCallback,Tunable,DarkReceiver,ConfigurationListener。主要是监听电池状态变化,白天黑夜模式,以及主题等变化。
public class BatteryMeterView extends LinearLayout implements
BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {}
而锁屏界面的状态栏也是引用到这个BatteryMeterView,在路径SystemUI\src\com\android\systemui\statusbar\phone\KeyguardStatusBarView.java中
private BatteryMeterView mBatteryView;
OK,明白了大概的逻辑,就可以自己试着重构了。
三、自定义电池图标
如果在原文件 BatteryMeterView中修改太过麻烦,所以我们可以模仿BatteryMeterView,直接自己写一个自定义View。
直接上主菜!
1.在system_icons.xml中替换布局。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/system_icons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<!--add by huangjiawei,replace bird battery meter view
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
-->
<include layout="@layout/bird_status_bar_battery"/>
<!-- @} -->
</LinearLayout>
bird_status_bar_battery.xml
<?xml version="1.0" encoding="utf-8"?>
<com.bird.systemui.BirdBatteryMeterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/battery"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/battery_outside_percent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:paddingRight="2dp"
android:textColor="@android:color/white"
android:textSize="@dimen/bird_battery_outside_text_size"
android:visibility="gone" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<ImageView
android:id="@+id/battery_border"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/bird_ic_statusbar_battery" />
<ImageView
android:id="@+id/battery_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/bird_stat_sys_battery_level_list" />
<ImageView
android:id="@+id/battery_inside_charge_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:scaleType="centerInside"
android:src="@drawable/bird_ic_statusbar_battery_charge_icon"
android:visibility="gone" />
<TextView
android:id="@+id/battery_inside_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingEnd="2dp"
android:textColor="@android:color/white"
android:textSize="@dimen/bird_battery_inside_text_size"
android:visibility="gone" />
</FrameLayout>
<ImageView
android:id="@+id/battery_outside_charge_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingStart="2dp"
android:src="@drawable/bird_ic_statusbar_battery_charge_icon"
android:visibility="gone" />
</com.bird.systemui.BirdBatteryMeterView>
可以看到,上面的布局是一个横向的LinearLayout。
1.其中有outside和inside的电量百分比,百分比的显示或隐藏可以在设置-电池中去控制,也可以直接写死一种方式。
2.outside的充电图标和inside的充电图标,就是开始充电后显示的闪电图标,可以选择在电池内也可以选择在外侧。充电图标和电量百分比是对立的,如果充电图标inside,则电量百分比outside,反之亦然。
3.border是电池的边界,就是最外的一圈。
4.charge_level用list来管理,一共有10来个等级。电量的多少就是从这里体现。
2.布局中的src都是矢量图,具体的矢量图代码在另一篇文章中。可以参考一下。
3.自定义view,BirdBatteryMeterView同样继承于LinearLayout
package com.bird.systemui;
import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.PowerManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService.Tunable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Optional;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import static com.android.systemui.DejankUtils.whitelistIpcs;
//[状态栏更换新的电池图标][huangjiawei][20230427]
public class BirdBatteryMeterView extends LinearLayout implements
BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
private ImageView mBorderView;
private ImageView mInsideChargeIconView;
private ImageView mOutsideChargeIconView;
private ImageView mLevelView;
private TextView mInsidePercentView;
private TextView mOutsidePercentView;
private BatteryController mBatteryController;
private int mUser;
private final SettingObserver mSettingObserver;
private final boolean mShowPercentAvailable;
private final CurrentUserTracker mUserTracker;
private int mNonAdaptedColor;
private final String mSlotBattery;
private int mLevel;
private boolean mShowPercent;
private boolean mShowPercentIn;
private boolean mIsPowerSave;
private boolean mCharging;
public BirdBatteryMeterView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BirdBatteryMeterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
BroadcastDispatcher broadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
mShowPercentAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
mSlotBattery = context.getString(
com.android.internal.R.string.status_bar_battery);
mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
@Override
public void onUserSwitched(int newUserId) {
mUser = newUserId;
getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
getContext().getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver,
newUserId);
updateShowPercent();
updatePercentLevel();
}
};
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBorderView = findViewById(R.id.battery_border);
mLevelView = findViewById(R.id.battery_level);
mInsidePercentView = findViewById(R.id.battery_inside_percent);
mOutsidePercentView = findViewById(R.id.battery_outside_percent);
mInsideChargeIconView = findViewById(R.id.battery_inside_charge_icon);
mOutsideChargeIconView = findViewById(R.id.battery_outside_charge_icon);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mBatteryController = Dependency.get(BatteryController.class);
mBatteryController.addCallback(this);
mUser = ActivityManager.getCurrentUser();
getContext().getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser);
getContext().getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
false, mSettingObserver);
mUserTracker.startTracking();
updateShowPercent();
updatePercentLevel();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mUserTracker.stopTracking();
mBatteryController.removeCallback(this);
getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
}
@Override
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint);
mOutsidePercentView.setTextColor(mNonAdaptedColor);
mInsidePercentView.setTextColor(mNonAdaptedColor);
mInsideChargeIconView.setColorFilter(mNonAdaptedColor);
mOutsideChargeIconView.setColorFilter(mNonAdaptedColor);
if (!mIsPowerSave) {
mBorderView.setColorFilter(mNonAdaptedColor);
}
if (!isLowLevel() || mCharging) {
mLevelView.setColorFilter(mNonAdaptedColor);
}
}
@Override
public void onTuningChanged(String key, String newValue) {
if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
ArraySet<String> icons = StatusBarIconController.getIconHideList(
getContext(), newValue);
setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
}
}
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mLevel = level;
mCharging = charging;
updatePercentLevel();
updatePercentText();
}
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
PowerManager powerManager = getContext().getSystemService(PowerManager.class);
mIsPowerSave = powerManager.isPowerSaveMode();
if (mIsPowerSave) {
mBorderView.setColorFilter(mContext.getColor(R.color.bird_battry_power_save_color));
} else {
mBorderView.setColorFilter(mNonAdaptedColor);
}
}
private final class SettingObserver extends ContentObserver {
public SettingObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
super.onChange(selfChange, uri);
updateShowPercent();
updatePercentLevel();
if (TextUtils.equals(uri.getLastPathSegment(),
Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
updatePercentText();
}
}
}
private void updatePercentText() {
if (mBatteryController == null) {
return;
}
if (mOutsidePercentView != null || mInsidePercentView != null) {
if (!mCharging) {
mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
if (estimate != null) {
Optional.ofNullable(mOutsidePercentView).ifPresent(view -> view.setText(estimate));
Optional.ofNullable(mInsidePercentView).ifPresent(view -> view.setText(estimate));
setContentDescription(getContext().getString(
R.string.accessibility_battery_level_with_estimate,
mLevel, estimate));
} else {
setPercentTextAtCurrentLevel();
}
});
} else {
setPercentTextAtCurrentLevel();
}
} else {
setContentDescription(
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
: R.string.accessibility_battery_level, mLevel));
}
}
private void setPercentTextAtCurrentLevel() {
mOutsidePercentView.setText(
NumberFormat.getPercentInstance().format(mLevel / 100f));
mInsidePercentView.setText(NumberFormat.getNumberInstance().format(mLevel));
setContentDescription(
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
: R.string.accessibility_battery_level, mLevel));
}
private void updateShowPercent() {
//读设置中是否显示电量百分比的值
mShowPercent = 0 != whitelistIpcs(() -> Settings.System
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, mUser));
//这个值同样可以在设置-电池中加两项去让用户决定用哪种方式,也可以直接像这里的值一样,直接写死一种方式。
mShowPercentIn = true;
if (mShowPercentAvailable && mShowPercent) {
mOutsidePercentView.setVisibility(!mShowPercentIn ? View.VISIBLE : View.GONE);
mInsidePercentView.setVisibility(mShowPercentIn ? View.VISIBLE : View.GONE);
updatePercentText();
} else {
mOutsidePercentView.setVisibility(View.GONE);
mInsidePercentView.setVisibility(View.GONE);
}
}
private void updatePercentLevel() {
//电量百分比和充电图标出现的位置是对立的
boolean showInsidePercent = mShowPercent && mShowPercentIn;
mInsideChargeIconView.setVisibility(mCharging && !showInsidePercent
? View.VISIBLE : View.GONE);
mOutsideChargeIconView.setVisibility(mCharging && showInsidePercent
? View.VISIBLE : View.GONE);
mLevelView.setImageLevel(mLevel);
//设置透明度可以调整充电body的颜色
if (mCharging) {
mLevelView.setColorFilter(mNonAdaptedColor);
mLevelView.setAlpha(0.6f);
} else {
if (isLowLevel()) {
mLevelView.clearColorFilter();
mLevelView.setAlpha(1.0f);
} else {
mLevelView.setColorFilter(mNonAdaptedColor);
if (showInsidePercent) {
mLevelView.setAlpha(0.3f);
} else {
mLevelView.setAlpha(1.0f);
}
}
}
}
private boolean isLowLevel() {
return mLevel <= 20;
}
// copy from BatteryMeterView, caller exists
public void setForceShowPercent(boolean show) {
}
// copy from BatteryMeterView, caller exists
public void setColorsFromContext(Context context) {
}
// copy from BatteryMeterView, caller exists
public void updatePercentView() {
}
// copy from BatteryMeterView, caller exists
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
}
}
4.在锁屏的状态栏也更换一下自定义View就完事儿了,SystemUI\src\com\android\systemui\statusbar\phone\KeyguardStatusBarView.java
/**
* The header group on Keyguard.
*/
public class KeyguardStatusBarView extends RelativeLayout implements
BatteryStateChangeCallback,
OnUserInfoChangedListener,
ConfigurationListener,
SystemStatusAnimationCallback {
........
private ImageView mMultiUserAvatar;
//[状态栏更换新的电池图标][huangjiawei][20230427]begin
//private BatteryMeterView mBatteryView;
private BirdBatteryMeterView mBatteryView;
//[状态栏更换新的电池图标][huangjiawei][20230427]end
private StatusIconContainer mStatusIconContainer;
private BatteryController mBatteryController;
........
}
四、总结
至此,更换android系统状态栏的电池图标就完成了。自己可以根据需要修改里面的参数,例如宽高,颜色,透明度等等,也可以在设置里添加控制电量百分比和充电图标的选项,仅作参考。