手机中状态栏主要用来显示电池电量信息、时间、信号格数、系统图标(闹钟)、通知图标,我们先来看看手机statusbar的界面
今天我们先来简单介绍下这个界面是怎么显示出来,考虑到放到一起写,文章就有点太长了,后续会对信号格图标显示、通知图标、系统图标这几个复杂点的一一介绍
从上图中我们基本可以看出,从左到右基本上是通知图标显示区域、系统图标显示区域,系统图标区域里主要包括wifi、飞行模式、闹钟、耳机、信号格显示、数据业务上下行箭头、电池电量图标、时间图标,具体布局如下:
//frameworks/base/packages/SystemUI/res/layout/status_bar.xml
<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:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
>
...
"@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="6dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal" />
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
"@layout/system_icons" />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_vertical|start"
/>
com.android.keyguard.AlphaOptimizedLinearLayout>
...
com.android.systemui.statusbar.phone.PhoneStatusBarView>
如果有通知图标相关问题,基本上就可以根据notification_icon_area这个id去跟踪就行,而系统图标区域还有一个比较重要的布局文件system_icons.xml
//frameworks/base/packages/SystemUI/res/layout/system_icons.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical">
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<include layout="@layout/signal_cluster_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="@dimen/status_bar_battery_icon_height"
android:layout_width="@dimen/status_bar_battery_icon_width"
android:layout_marginBottom="@dimen/battery_margin_bottom"/>
LinearLayout>
这个里面的signal_cluster_view.xml主要是和sim卡相关的一些图标(、信号格数、网络状态、数据业务上下行、漫游等)、以及wifi、飞行模式图标,BatteryMeterView主要用来显示电池电量信息的
//frameworks/base/packages/SystemUI/res/layout/signal_cluster_view.xml
<com.android.systemui.statusbar.SignalClusterView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/signal_cluster"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="@dimen/signal_cluster_battery_padding" >
"@+id/"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingEnd="6dp"
android:src="@drawable/stat_sys__ic"
/>
"@+id/ethernet_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
> <com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneLightTheme"
android:id="@+id/ethernet"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/> <com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneDarkTheme"
android:id="@+id/ethernet_dark"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:alpha="0.0"
/>
"@+id/wifi_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
> <com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneLightTheme"
android:id="@+id/wifi_signal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/> <com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneDarkTheme"
android:id="@+id/wifi_signal_dark"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:alpha="0.0"
/>
"@+id/wifi_signal_spacer" android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
android:layout_height="4dp"
android:visibility="gone"
/>
"@+id/mobile_signal_group"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
"@+id/no_sims_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content" android:contentDescription="@string/accessibility_no_sims">
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneLightTheme"
android:id="@+id/no_sims"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/stat_sys_no_sims"
/>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:theme="@style/DualToneDarkTheme"
android:id="@+id/no_sims_dark"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/stat_sys_no_sims"
android:alpha="0.0"
/>
"@+id/wifi_airplane_spacer" android:layout_width="@dimen/status_bar_airplane_spacer_width"
android:layout_height="4dp"
android:visibility="gone"
/>
"@+id/airplane"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
com.android.systemui.statusbar.SignalClusterView>
基本布局文件差不多介绍完了,下面画了个简单的图总结下
下面介绍完了布局,就来看看这些布局是在那儿被加载的,以及电池电量、和时间图标的具体显示流程
status_bar.xml文件是在super_status_bar.xml里被include的,而super_status_bar.xml的加载是在PhoneStatusBar.java里的makeStatusBarView中初始化的,如下
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
// ================================================================================
// Constructing the view
// ================================================================================
protected PhoneStatusBarView makeStatusBarView() {
...
inflateStatusBarWindow(context);
mStatusBarWindow.setService(this);
mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
checkUserAutohide(v, event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
animateCollapsePanels();
}
}
return mStatusBarWindow.onTouchEvent(event);
}
});
mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
// set the initial view visibility
setAreThereNotifications();
mBatteryController = createBatteryController();
mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
mHandler.post(mCheckBarModes);
if (mDozeServiceHost != null) {
mDozeServiceHost.firePowerSaveChanged(isPowerSave);
}
}
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
// noop
}
});
mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
mNetworkController.setUserSetupComplete(mUserSetup);
mHotspotController = new HotspotControllerImpl(mContext);
mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
mSecurityController = new SecurityControllerImpl(mContext);
if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
mRotationLockController = new RotationLockControllerImpl(mContext);
}
initSignalCluster(mStatusBarView);
initSignalCluster(mKeyguardStatusBar);
initEmergencyCryptkeeperText();
mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
...
((BatteryMeterView) mStatusBarView.findViewById(R.id.battery)).setBatteryController(
mBatteryController);
mKeyguardStatusBar.setBatteryController(mBatteryController);
...
return mStatusBarView;
}
基本上状态里显示的入口就在这,下面主要看看电池电量、和时间图标,从前面布局文件可知,显示时间对应的view为Clock.java,电池电量的为BatteryMeterView.java
时间的更新主要是通过监听‘android.intent.action.TIME_TICK’广播
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
public class Clock extends TextView implements DemoMode, Tunable {
...
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);//监听广播
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter,
null, getHandler());
TunerService.get(getContext()).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_BLACKLIST);
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
// in the main thread, therefore the receiver can't run before this method returns.
// The time zone may have changed while the receiver wasn't registered, so update the Time
mCalendar = Calendar.getInstance(TimeZone.getDefault());
// Make sure we update to the current time
updateClock();
updateShowSeconds();
}
...
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz));
if (mClockFormat != null) {
mClockFormat.setTimeZone(mCalendar.getTimeZone());
}
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
final Locale newLocale = getResources().getConfiguration().locale;
if (! newLocale.equals(mLocale)) {
mLocale = newLocale;
mClockFormatString = ""; // force refresh
}
}
updateClock();//更新时间
}
};
final void updateClock() {
if (mDemoMode) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
setText(getSmallTime());
setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
}
//格式化时间
private final CharSequence getSmallTime() {
Context context = getContext();
boolean is24 = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser());
LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
final char MAGIC1 = '\uEF00';
final char MAGIC2 = '\uEF01';
SimpleDateFormat sdf;
String format = mShowSeconds
? is24 ? d.timeFormat_Hms : d.timeFormat_hms
: is24 ? d.timeFormat_Hm : d.timeFormat_hm;
if (!format.equals(mClockFormatString)) {
mContentDescriptionFormat = new SimpleDateFormat(format);
/*
* Search for an unquoted "a" in the format string, so we can
* add dummy characters around it to let us find it again after
* formatting and change its size.
*/
if (mAmPmStyle != AM_PM_STYLE_NORMAL) {
int a = -1;
boolean quoted = false;
for (int i = 0; i < format.length(); i++) {
char c = format.charAt(i);
if (c == '\'') {
quoted = !quoted;
}
if (!quoted && c == 'a') {
a = i;
break;
}
}
if (a >= 0) {
// Move a back so any whitespace before AM/PM is also in the alternate size.
final int b = a;
while (a > 0 && Character.isWhitespace(format.charAt(a-1))) {
a--;
}
format = format.substring(0, a) + MAGIC1 + format.substring(a, b)
+ "a" + MAGIC2 + format.substring(b + 1);
}
}
mClockFormat = sdf = new SimpleDateFormat(format);
mClockFormatString = format;
} else {
sdf = mClockFormat;
}
String result = sdf.format(mCalendar.getTime());
if (mAmPmStyle != AM_PM_STYLE_NORMAL) {
int magic1 = result.indexOf(MAGIC1);
int magic2 = result.indexOf(MAGIC2);
if (magic1 >= 0 && magic2 > magic1) {
SpannableStringBuilder formatted = new SpannableStringBuilder(result);
if (mAmPmStyle == AM_PM_STYLE_GONE) {
formatted.delete(magic1, magic2+1);
} else {
if (mAmPmStyle == AM_PM_STYLE_SMALL) {
CharacterStyle style = new RelativeSizeSpan(0.7f);
formatted.setSpan(style, magic1, magic2,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
formatted.delete(magic2, magic2 + 1);
formatted.delete(magic1, magic1 + 1);
}
return formatted;
}
}
...
return result;
}
}
电池电量图标这个和时钟差不多,主要是BatteryMeterView(ImageView的子类)和BatteryMeterDrawable(Drawable的子类)这两个类,其中BatteryMeterView负责view显示,BatteryMeterDrawable负责图标内容更新,并且这两个类都实现了BatteryController.BatteryStateChangeCallback这个接口
public interface BatteryController extends DemoMode {
...
/**
* A listener that will be notified whenever a change in battery level or power save mode
* has occurred.
*/
interface BatteryStateChangeCallback {
void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
void onPowerSaveChanged(boolean isPowerSave);
}
}
frameworks/base/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
public class BatteryMeterDrawable extends Drawable implements
BatteryController.BatteryStateChangeCallback {
...
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mLevel = level;
mPluggedIn = pluggedIn;
postInvalidate();//更新图标
}
@Override
public void draw(Canvas c) {
...
}
}
onBatteryLevelChanged是在BatteryControllerImpl.java里被调用的
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
...
public BatteryControllerImpl(Context context) {
mContext = context;
mHandler = new Handler();
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
registerReceiver();
updatePowerSave();
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED); //注册电量改变的广播
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
filter.addAction(ACTION_LEVEL_TEST);
mContext.registerReceiver(this, filter);
}
@Override
public void onReceive(final Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
...
fireBatteryLevelChanged();
}
}
...
protected void fireBatteryLevelChanged() {
synchronized (mChangeCallbacks) {
final int N = mChangeCallbacks.size();
for (int i = 0; i < N; i++) {
mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
}
}
}
...
}
后续会对信号格图标显示、通知图标、系统图标都单独分析介绍,如有错误,麻烦留言指出。