你真的了解你手机的状态栏吗?
16lz
2021-12-04
今天来看一下android 手机的状态栏的源码流程分析。
相关类
frameworks\base\services\java\com\android\server\SystemServer.javaframeworks\base\packages\SystemUI\src\com\android\systemui\SystemUIService.javaframeworks\base\packages\SystemUI\src\com\android\systemui\SystemUIApplication.javaframeworks\base\packages\SystemUI\res\values\config.xmlsystemui\app\src\main\java\com\android\systemui\statusbar\phone\StatusBar.javaapp\src\main\java\com\android\systemui\statusbar\phone\CollapsedStatusBarFragmentapp\src\main\java\com\android\systemui\statusbar\phone\StatusBarWindowManagerapp\src\main\java\com\android\systemui\statusbar\phone\PhoneStatusBarPolicy.javasettingslib\src\main\java\com\android\settingslib\graph\BatteryMeterDrawableBase
1.systemUI的启动
frameworks\base\services\java\com\android\server\SystemServer.javastatic final void startSystemUi(Context context, WindowManagerService windowManager) { Intent intent = new Intent(); intent.setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); //Slog.d(TAG, "Starting service: " + intent); context.startServiceAsUser(intent, UserHandle.SYSTEM); windowManager.onSystemUiStarted(); }
frameworks\base\packages\SystemUI\src\com\android\systemui\SystemUIService.java @Override public void onCreate() { super.onCreate(); ((SystemUIApplication) getApplication()).startServicesIfNeeded();// 在这里启动 // For debugging RescueParty if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) { throw new RuntimeException(); } if (Build.IS_DEBUGGABLE) { // b/71353150 - looking for leaked binder proxies BinderInternal.nSetBinderProxyCountEnabled(true); BinderInternal.nSetBinderProxyCountWatermarks(1000,900); BinderInternal.setBinderProxyCountCallback( new BinderInternal.BinderProxyLimitListener() { @Override public void onLimitReached(int uid) { Slog.w(SystemUIApplication.TAG, "uid " + uid + " sent too many Binder proxies to uid " + Process.myUid()); } }, Dependency.get(Dependency.MAIN_HANDLER)); } }
frameworks\base\packages\SystemUI\src\com\android\systemui\SystemUIApplication.java public void startServicesIfNeeded() { String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents); startServicesIfNeeded(names); }//这里可以看到有一个array的数组需要启动 private void startServicesIfNeeded(String[] services) { if (mServicesStarted) { return; } mServices = new SystemUI[services.length]; if (!mBootCompleted) { // check to see if maybe it was already completed long before we began // see ActivityManagerService.finishBooting() if ("1".equals(SystemProperties.get("sys.boot_completed"))) { mBootCompleted = true; if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent"); } } Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + "."); TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", Trace.TRACE_TAG_APP); log.traceBegin("StartServices"); final int N = services.length; for (int i = 0; i < N; i++) { String clsName = services[i]; if (DEBUG) Log.d(TAG, "loading: " + clsName); log.traceBegin("StartServices" + clsName); long ti = System.currentTimeMillis(); Class cls; try { cls = Class.forName(clsName); mServices[i] = (SystemUI) cls.newInstance(); } catch(ClassNotFoundException ex){ throw new RuntimeException(ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InstantiationException ex) { throw new RuntimeException(ex); } mServices[i].mContext = this; mServices[i].mComponents = mComponents; if (DEBUG) Log.d(TAG, "running: " + mServices[i]); mServices[i].start(); // 每个都从这里启动 log.traceEnd(); // Warn if initialization of component takes too long ti = System.currentTimeMillis() - ti; if (ti > 1000) { Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms"); } if (mBootCompleted) { mServices[i].onBootCompleted(); } }
这里其实大家不要被名称搞混了,这不是服务,而是继承systemUI的类。通过start方法去加载启动
frameworks\base\packages\SystemUI\res\values\config.xml这里可以看到有这么多类需要启动加载。 <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.Dependencyitem> <item>com.android.systemui.util.NotificationChannelsitem> <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStartitem> <item>com.android.systemui.keyguard.KeyguardViewMediatoritem> <item>com.android.systemui.recents.Recentsitem> <item>com.android.systemui.volume.VolumeUIitem> <item>com.android.systemui.stackdivider.Divideritem> <item>com.android.systemui.SystemBarsitem> <item>com.android.systemui.usb.StorageNotificationitem> <item>com.android.systemui.power.PowerUIitem> <item>com.android.systemui.media.RingtonePlayeritem> <item>com.android.systemui.keyboard.KeyboardUIitem> <item>com.android.systemui.pip.PipUIitem> <item>com.android.systemui.shortcut.ShortcutKeyDispatcheritem> <item>@string/config_systemUIVendorServiceComponentitem> <item>com.android.systemui.util.leak.GarbageMonitor$Serviceitem> <item>com.android.systemui.LatencyTesteritem> <item>com.android.systemui.globalactions.GlobalActionsComponentitem> <item>com.android.systemui.ScreenDecorationsitem> <item>com.android.systemui.fingerprint.FingerprintDialogImplitem> <item>com.android.systemui.SliceBroadcastRelayHandleritem> string-array>
这里只分析com.android.systemui.SystemBars的流程
systemui\app\src\main\java\com\android\systemui\SystemBars @Override public void start() { if (DEBUG) Log.d(TAG, "start"); createStatusBrFromConfig(); } private void createStatusBarFromConfig() { if (DEBUG) Log.d(TAG, "createStatusBarFromConfig"); final String clsName = mContext.getString(R.string.config_statusBarComponent); // com.android.systemui.statusbar.phone.StatusBar // 这里这个采用反射 if (clsName == null || clsName.length() == 0) { throw andLog("No status bar component configured", null); } Class<?> cls = null; try { cls = mContext.getClassLoader().loadClass(clsName); } catch (Throwable t) { throw andLog("Error loading status bar component: " + clsName, t); } try { mStatusBar = (SystemUI) cls.newInstance(); } catch (Throwable t) { throw andLog("Error creating status bar component: " + clsName, t); } mStatusBar.mContext = mContext; mStatusBar.mComponents = mComponents; mStatusBar.start(); if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName()); }
systemui\app\src\main\java\com\android\systemui\statusbar\phone\StatusBar.java @Override public void start() { ... 省略部分代码,只保留文章相关的 createAndAddWindows() ; ... } private void addStatusBarWindow() { makeStatusBarView();// 调用此处方法 mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this, new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.row.notifyHeightChanged(true /* needsAnimation */); updateFooter(); } public void lockScrollTo(NotificationData.Entry entry) { mStackScroller.lockScrollTo(entry.row); } public void requestDisallowLongPressAndDismiss() { mStackScroller.requestDisallowLongPress(); mStackScroller.requestDisallowDismiss(); } }); mRemoteInputManager.getController().addCallback(mStatusBarWindowManager); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); // 这个方法里面其实就可以看出是个window } protected void makeStatusBarView() { inflateStatusBarWindow(context); // 加载布局,下面分析 FragmentHostManager.get(mStatusBarWindow) .addTagListener(CollapsedStatusBarFragment.TAG, new FragmentHostManager.FragmentListener() { @Override public void onFragmentViewCreated(String tag, Fragment fragment) { CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment; //这里这个fragment 才是我们的状态栏的fragment statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); mStatusBarView = (PhoneStatusBarView) fragment.getView(); mStatusBarView.setBar(StatusBar.this); mStatusBarView.setPanel(mNotificationPanel); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setBouncerShowing(mBouncerShowing); if (mHeadsUpAppearanceController != null) { // This view is being recreated, let's destroy the old one mHeadsUpAppearanceController.destroy(); } mHeadsUpAppearanceController = new HeadsUpAppearanceController( mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow); StatusBar.this.setAreThereNotifications(); StatusBar.this.checkBarModes(); /// M: add for plmn display feature @{ StatusBar.this.attachPlmnPlugin(); ///@} } }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG) // 这可以看到这里也是在加载这个fragment 状态栏 .commit(); mIconController = Dependency.get(StatusBarIconController.class);} // 加载布局 protected void inflateStatusBarWindow(Context context) { mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null); }
app\src\main\java\com\android\systemui\statusbar\phone\CollapsedStatusBarFragment @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.status_bar, container, false); // 这个布局文件就是我们手机的状态栏 }
app\src\main\java\com\android\systemui\statusbar\phone\StatusBarWindowManager public void add(View statusBarView, int barHeight) { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. mLp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, barHeight, WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mLp.setTitle("StatusBar"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mStatusBarView = statusBarView; mBarHeight = barHeight; mWindowManager.addView(mStatusBarView, mLp); // 可以看到这个其实也是一个window mLpChanged = new WindowManager.LayoutParams(); mLpChanged.copyFrom(mLp); }
到此,状态栏就启动了。接下来说说状态栏的加载以及布局
2.状态栏布局
super_status_bar.xml<com.android.systemui.statusbar.phone.StatusBarWindowView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <com.android.systemui.statusbar.BackDropView android:id="@+id/backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" sysui:ignoreRightInset="true"> <ImageView android:id="@+id/backdrop_back" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> <ImageView android:id="@+id/backdrop_front" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:visibility="invisible" /> </com.android.systemui.statusbar.BackDropView> <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" sysui:ignoreRightInset="true" /> <FrameLayout android:id="@+id/status_bar_container" // 这个fragment就是我们之前说的那个状态栏的fragment android:layout_width="match_parent" android:layout_height="wrap_content" /> <ViewStub android:id="@+id/fullscreen_user_switcher_stub" android:layout_width="match_parent" android:layout_height="match_parent" android:layout="@layout/car_fullscreen_user_switcher" /> <include layout="@layout/status_bar_expanded" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> <include layout="@layout/brightness_mirror" /> <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" sysui:ignoreRightInset="true" /></com.android.systemui.statusbar.phone.StatusBarWindowView>
// 真正状态栏的显示
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:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" android:id="@+id/status_bar" android:background="@drawable/system_bar_background" 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:orientation="horizontal" > <ViewStub android:id="@+id/operator_name" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/operator_name" /> <FrameLayout android:layout_height="match_parent" android:layout_width="0dp" android:layout_weight="1"> <include layout="@layout/heads_up_status_bar_layout" /> <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and DISABLE_NOTIFICATION_ICONS, respectively --> <LinearLayout android:id="@+id/status_bar_left_side" android:layout_height="match_parent" android:layout_width="match_parent" android:clipChildren="false" > // 时间显示 <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:layout_width="wrap_content" android:layout_height="match_parent" android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:singleLine="true" android:paddingStart="@dimen/status_bar_left_clock_starting_padding" android:paddingEnd="@dimen/status_bar_left_clock_end_padding" android:gravity="center_vertical|start" /> // 通知显示区域 <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/notification_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:clipChildren="false"/> </LinearLayout> </FrameLayout> <!-- Space should cover the notch (if it exists) and let other views lay out around it --> <android.widget.Space android:id="@+id/cutout_space_view" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="center_horizontal|center_vertical" />// 系统icon <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:gravity="center_vertical|end" > <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.xml这里主要就是显示一些系统的图标,包括闹钟,wifi。蓝牙等,系统中这些图片都是矢量图<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 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" /></LinearLayout>
总结下来状态栏如下所示
接下来看一下系统icon 栏是如何显示的
app\src\main\java\com\android\systemui\statusbar\phone\PhoneStatusBarPolicy.java系统icon 栏的初始化和更新都是在这个类里面去实现的 @VisibleForTesting public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) { mContext = context; mIconController = iconController; mCast = Dependency.get(CastController.class); mHotspot = Dependency.get(HotspotController.class); mBluetooth = Dependency.get(BluetoothController.class); mNextAlarmController = Dependency.get(NextAlarmController.class); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mUserInfoController = Dependency.get(UserInfoController.class); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mRotationLockController = Dependency.get(RotationLockController.class); mDataSaver = Dependency.get(DataSaverController.class); mZenController = Dependency.get(ZenModeController.class); mProvisionedController = Dependency.get(DeviceProvisionedController.class); mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mLocationController = Dependency.get(LocationController.class); mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot); mSlotBluetooth = context.getString(com.android.internal.R.string.status_bar_bluetooth); mSlotTty = context.getString(com.android.internal.R.string.status_bar_tty); mSlotZen = context.getString(com.android.internal.R.string.status_bar_zen); mSlotVolume = context.getString(com.android.internal.R.string.status_bar_volume); mSlotAlarmClock = context.getString(com.android.internal.R.string.status_bar_alarm_clock); mSlotManagedProfile = context.getString( com.android.internal.R.string.status_bar_managed_profile); mSlotRotate = context.getString(com.android.internal.R.string.status_bar_rotate); mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset); mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver); mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location); // systemicon 的初始化 // listen for broadcasts IntentFilter filter = new IntentFilter(); /// M: [Multi-User] Will register this action using special receiver. //filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.ACTION_HEADSET_PLUG); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); /// M: [Multi-User] Add user switched action for updating possible alarm icon. filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); // 广播的注册 /// M: Add icon for embms session. filter.addAction(TelephonyIntents.ACTION_EMBMS_SESSION_STATUS_CHANGED); filter.addAction(SMART_DEVICE_BROADCAST); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); /// M: [Multi-User] Register Alarm intent by user registerAlarmClockChanged(UserHandle.USER_OWNER, false); // Alarm clock mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null); mIconController.setIconVisibility(mSlotAlarmClock, false);接受到广播后去更新icon private void updateAlarm() { final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0; int zen = mZenController.getZen(); final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim : R.drawable.stat_sys_alarm, buildAlarmContentDescription()); mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm); } // 广播接收器@VisibleForTesting /*private*/ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case AudioManager.RINGER_MODE_CHANGED_ACTION: case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION: updateVolumeZen(); break; case TelephonyIntents.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; } updateSimState(intent); break; case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED: updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE, TelecomManager.TTY_MODE_OFF)); break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: case Intent.ACTION_MANAGED_PROFILE_REMOVED: updateManagedProfile(); break; case AudioManager.ACTION_HEADSET_PLUG: updateHeadsetPlug(intent); break; case Intent.ACTION_USER_SWITCHED: updateAlarm(); int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); registerAlarmClockChanged(newUserId, true); break; case TelephonyIntents.ACTION_EMBMS_SESSION_STATUS_CHANGED: updateEmbmsState(intent); break; case SMART_DEVICE_BROADCAST: updateSmartDevice(intent); break; } } };
所以如果,需要在systemicon 栏客制化加入一个其他的icon,则可以在这个类里面仿照做修改
4. 电池电量的更新
从上面的xml 中可以看到电池电量的UI是在BatteryMeterView.java 类中
而真正去绘制电视的UI显示则是在settingslib的BatteryMeterDrawableBase.java 类中
settingslib\src\main\java\com\android\settingslib\graph\BatteryMeterDrawableBase // 初始化画笔 public BatteryMeterDrawableBase(Context context, int frameColor) { mContext = context; final Resources res = context.getResources(); TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels); TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values); final int N = levels.length(); mColors = new int[2 * N]; for (int i = 0; i < N; i++) { mColors[2 * i] = levels.getInt(i, 0); if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) { mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0)); } else { mColors[2 * i + 1] = colors.getColor(i, 0); } } levels.recycle(); colors.recycle(); mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol); mCriticalLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_criticalBatteryWarningLevel); mButtonHeightFraction = context.getResources().getFraction( R.fraction.battery_button_height_fraction, 1, 1); mSubpixelSmoothingLeft = context.getResources().getFraction( R.fraction.battery_subpixel_smoothing_left, 1, 1); mSubpixelSmoothingRight = context.getResources().getFraction( R.fraction.battery_subpixel_smoothing_right, 1, 1); mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFramePaint.setColor(frameColor); mFramePaint.setDither(true); mFramePaint.setStrokeWidth(0); mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE); mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBatteryPaint.setDither(true); mBatteryPaint.setStrokeWidth(0); mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD); mTextPaint.setTypeface(font); mTextPaint.setTextAlign(Paint.Align.CENTER); mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); font = Typeface.create("sans-serif", Typeface.BOLD); mWarningTextPaint.setTypeface(font); mWarningTextPaint.setTextAlign(Paint.Align.CENTER); if (mColors.length > 1) { mWarningTextPaint.setColor(mColors[1]); } mChargeColor = Utils.getDefaultColor(mContext, android.R.color.holo_red_dark); mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBoltPaint.setColor(Utils.getDefaultColor(mContext, android.R.color.holo_green_dark)); mBoltPoints = loadPoints(res, R.array.batterymeter_bolt_points); mPlusPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPlusPaint.setColor(Utils.getDefaultColor(mContext, R.color.batterymeter_plus_color)); mPlusPoints = loadPoints(res, R.array.batterymeter_plus_points); mPowersavePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPowersavePaint.setColor(mPlusPaint.getColor()); mPowersavePaint.setStyle(Style.STROKE); mPowersavePaint.setStrokeWidth(context.getResources() .getDimensionPixelSize(R.dimen.battery_powersave_outline_thickness)); mIntrinsicWidth = context.getResources().getDimensionPixelSize(R.dimen.battery_width); mIntrinsicHeight = context.getResources().getDimensionPixelSize(R.dimen.battery_height); }// 画电池 @Override public void draw(Canvas c) { final int level = mLevel; final Rect bounds = getBounds(); if (level == -1) return; float drawFrac = (float) level / 100f; final int height = mHeight; final int width = (int) (getAspectRatio() * mHeight); final int px = (mWidth - width) / 2; final int buttonHeight = Math.round(height * mButtonHeightFraction); final int left = mPadding.left + bounds.left; final int top = bounds.bottom - mPadding.bottom - height; mFrame.set(left, top, width + left, height + top); mFrame.offset(px, 0); // button-frame: area above the battery body mButtonFrame.set( mFrame.left + Math.round(width * 0.28f), mFrame.top, mFrame.right - Math.round(width * 0.28f), mFrame.top + buttonHeight); // frame: battery body area mFrame.top += buttonHeight; // set the battery charging color mBatteryPaint.setColor(batteryColorForLevel(level)); if (level >= FULL) { drawFrac = 1f; } else if (level <= mCriticalLevel) { drawFrac = 0f; } final float levelTop = drawFrac == 1f ? mButtonFrame.top : (mFrame.top + (mFrame.height() * (1f - drawFrac))); // define the battery shape mShapePath.reset(); mOutlinePath.reset(); final float radius = getRadiusRatio() * (mFrame.height() + buttonHeight); mShapePath.setFillType(FillType.WINDING); mShapePath.addRoundRect(mFrame, radius, radius, Direction.CW); mShapePath.addRect(mButtonFrame, Direction.CW); mOutlinePath.addRoundRect(mFrame, radius, radius, Direction.CW); Path p = new Path(); p.addRect(mButtonFrame, Direction.CW); mOutlinePath.op(p, Op.XOR); if (mCharging) { // define the bolt shape // Shift right by 1px for maximal bolt-goodness final float bl = mFrame.left + mFrame.width() / 4f + 1; final float bt = mFrame.top + mFrame.height() / 6f; final float br = mFrame.right - mFrame.width() / 4f + 1; final float bb = mFrame.bottom - mFrame.height() / 10f; if (mBoltFrame.left != bl || mBoltFrame.top != bt || mBoltFrame.right != br || mBoltFrame.bottom != bb) { mBoltFrame.set(bl, bt, br, bb); mBoltPath.reset(); mBoltPath.moveTo( mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); for (int i = 2; i < mBoltPoints.length; i += 2) { mBoltPath.lineTo( mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(), mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height()); } mBoltPath.lineTo( mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); } float boltPct = (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top); boltPct = Math.min(Math.max(boltPct, 0), 1); if (boltPct <= BOLT_LEVEL_THRESHOLD) { // draw the bolt if opaque c.drawPath(mBoltPath, mBoltPaint); } else { // otherwise cut the bolt out of the overall shape mShapePath.op(mBoltPath, Path.Op.DIFFERENCE); } } else if (mPowerSaveEnabled) { // define the plus shape final float pw = mFrame.width() * 2 / 3; final float pl = mFrame.left + (mFrame.width() - pw) / 2; final float pt = mFrame.top + (mFrame.height() - pw) / 2; final float pr = mFrame.right - (mFrame.width() - pw) / 2; final float pb = mFrame.bottom - (mFrame.height() - pw) / 2; if (mPlusFrame.left != pl || mPlusFrame.top != pt || mPlusFrame.right != pr || mPlusFrame.bottom != pb) { mPlusFrame.set(pl, pt, pr, pb); mPlusPath.reset(); mPlusPath.moveTo( mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height()); for (int i = 2; i < mPlusPoints.length; i += 2) { mPlusPath.lineTo( mPlusFrame.left + mPlusPoints[i] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[i + 1] * mPlusFrame.height()); } mPlusPath.lineTo( mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height()); } // Always cut out of the whole shape, and sometimes filled colorError mShapePath.op(mPlusPath, Path.Op.DIFFERENCE); if (mPowerSaveAsColorError) { c.drawPath(mPlusPath, mPlusPaint); } } // compute percentage text boolean pctOpaque = false; float pctX = 0, pctY = 0; String pctText = null; if (!mCharging && !mPowerSaveEnabled && level > mCriticalLevel && mShowPercent) { mTextPaint.setColor(getColorForLevel(level)); mTextPaint.setTextSize(height * (SINGLE_DIGIT_PERCENT ? 0.75f : (mLevel == 100 ? 0.38f : 0.5f))); mTextHeight = -mTextPaint.getFontMetrics().ascent; pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level); pctX = mWidth * 0.5f + left; pctY = (mHeight + mTextHeight) * 0.47f + top; pctOpaque = levelTop > pctY; if (!pctOpaque) { mTextPath.reset(); mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath); // cut the percentage text out of the overall shape mShapePath.op(mTextPath, Path.Op.DIFFERENCE); } } // draw the battery shape background c.drawPath(mShapePath, mFramePaint); // draw the battery shape, clipped to charging level mFrame.top = levelTop; c.save(); c.clipRect(mFrame); c.drawPath(mShapePath, mBatteryPaint); c.restore(); if (!mCharging && !mPowerSaveEnabled) { if (level <= mCriticalLevel) { // draw the warning text final float x = mWidth * 0.5f + left; final float y = (mHeight + mWarningTextHeight) * 0.48f + top; c.drawText(mWarningString, x, y, mWarningTextPaint); } else if (pctOpaque) { // draw the percentage text c.drawText(pctText, pctX, pctY, mTextPaint); } } // Draw the powersave outline last if (!mCharging && mPowerSaveEnabled && mPowerSaveAsColorError) { c.drawPath(mOutlinePath, mPowersavePaint); } }
更多相关文章
- Android(安卓)ASE 脚本环境
- Android(安卓)启动过程
- Android(安卓)启动过程(2)
- Android(安卓)ListView分隔线
- 华为手机Android(安卓)Studio开发不显示Logcat解决办法
- Android开机启动分析(一)logo的显示
- android ListView 属性
- Android开源图表库介绍
- Android(安卓)屏幕设置