Android状态栏右侧添加图标并控制其显示状态
背景介绍
最近接到一个需求。Android机器外接一个21key的键盘。键盘上有一个绿色和黄色按键。其功能就是当按下时,再按其他键会上报不同的键值。我们的外国合作伙伴就发现了一个问题,机器的页面上没有显示出按下绿键或者黄键的状态,就是说用户可能不知道当前键盘是在哪种输入状态。所以要求我们加上一个提示出来。
先看下最终效果,当按下绿色按键后,显示一个g字母,表示green的意思,黄色按键就是y,yellow。
思路
介绍完背景了,就讲一下实现过程吧。
其实刚开始我能想到的是直接加一个notification。但是!需求只是给用户个提示而已,notification不止在状态栏会显示在下拉菜单也会有个通知。这就有点杀鸡用牛刀的感觉。所以最好是在StatusBar右侧,电量图标旁边加上一个指示图标。
实现过程
本文是基于android P实现的,开始时候真的小看StatusBar了,以为会很简单,没想到一入源码深似海,进去半天出不来。不知道小伙伴们有没有用Hierarchy view看过StatusBar,它的Tree View就相当复杂。下面是电池图标的位置。是不是头皮发麻。
看了很多博客,跟了一下流程找到了关键的类–PhoneStatusBarPolicy。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
PhoneStatusBarPolicy:定义了系统通知图标的设置策略,监听图标改变广播。
看下它的构造方法。
public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) { mContext = context; mIconController = iconController; //省略... mSlotInputType = context.getString(com.android.internal.R.string.status_bar_input_type); 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); // listen for broadcasts IntentFilter filter = new IntentFilter(); 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); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); // listen for user / profile change. try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG); } catch (RemoteException e) { // Ignore } // TTY status updateTTY(); // bluetooth status updateBluetooth(); // Alarm clock mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null); mIconController.setIconVisibility(mSlotAlarmClock, false); // zen mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null); mIconController.setIconVisibility(mSlotZen, false); // volume mIconController.setIcon(mSlotVolume, R.drawable.stat_sys_ringer_vibrate, null); mIconController.setIconVisibility(mSlotVolume, false); updateVolumeZen(); // cast mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null); mIconController.setIconVisibility(mSlotCast, false); // hotspot mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot, mContext.getString(R.string.accessibility_status_bar_hotspot)); mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled()); // managed profile mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status, mContext.getString(R.string.accessibility_managed_profile)); mIconController.setIconVisibility(mSlotManagedProfile, mManagedProfileIconVisible); //省略... }
可以看到在构造方法中注册了广播接收器mIntentReceiver,用来接收各种状态的改变,去更改对应的图标。
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 ACTION_INPUT_STATUS_CHANGED: updateInputType(intent); break; } } };
接下来看代码猜也能猜出来了。
// Alarm clock mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null); mIconController.setIconVisibility(mSlotAlarmClock, false);
很明显就是设置alarm图标的代码,第二行是控制显示还是隐藏。
ok,我们只需要模仿它来加上我们自己的图标就行了。
第一步声明Slot
其实就是个字符串而已。
frameworks/base/core/res/res/values/symbols.xml中加上声明
<java-symbol type="string" name="status_bar_input_type" />
在frameworks/base/core/res/res/values/config.xml的string-array name="config_statusBarIcons"中添加我们自己的slot。比如:
<item><xliff:g id="id">@string/status_bar_input_type</xliff:g></item>
然后继续添加
<string translatable="false" name="status_bar_input_type">input_type</string>
第二步
在PhoneStatusBarPolicy.java中添加图标。
先声明一个slot 和一个广播aciton
private final String mSlotInputType;private final String ACTION_INPUT_STATUS_CHANGED = "android.intent.action.INPUT_STATUS_CHANGED";
在构造方法中初始化
mSlotInputType = context.getString(com.android.internal.R.string.status_bar_input_type);//...省略代码.... filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); //添加的action filter.addAction(ACTION_INPUT_STATUS_CHANGED); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
第三步
在广播接收器中调用按键状态改变后的方法,去改变图标显示。
case ACTION_INPUT_STATUS_CHANGED: updateInputType(intent); break;
第四步
实现updateInputType方法
private int[] inputTypeArr = { R.drawable.y_button, //yellow R.drawable.g_button //green }; private final void updateInputType(Intent intent) { final int input = intent.getIntExtra(mSlotInputType, 0); if (input >= inputTypeArr.length) return; if(input < 0){ mIconController.setIconVisibility(mSlotInputType, false); return; } int iconId = inputTypeArr[input]; mIconController.setIcon(mSlotInputType, iconId, null); mIconController.setIconVisibility(mSlotInputType, true); }
相信大家都能看懂,如果intent传过来的参数小于零就隐藏图标,大于零的话根据参数选择显示y图标还是g图标。别忘了把图标加到drawable下面。
packages/SystemUI/res/drawable-hdpi/g_button.png
packages/SystemUI/res/drawable-hdpi/y_button.png
图标可以从阿里巴巴图标库下载。
至此就已经添加完毕。
使用的话只需发广播就可以了
/** * update Input Status */ public void updateInputStatus(int inputState){ Intent intent = new Intent(ACTION_INPUT_STATUS_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(mContext.getString(R.string.status_bar_input_type), inputState); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); }
我是放到PhoneWindowManager中的interceptKeyBeforeQueueing方法中调用的。因为这里可以拦截Keyevent事件,根据keycode,scancode判断是黄色还是绿色按键,然后再传入不同的inputState参数,来控制状态栏的图标。
说实话SystemUI代码还是挺复杂的,这里只是提供了添加图标的方法,并不涉及到StatusBar初始化流程,有兴趣的小伙伴可以自己尝试跟一下源码。
更多相关文章
- Android中Button点击事件实现的三种方式总结及Demo演示
- "必须搭配使用google play服务才能运行"或“您必须先更新Google
- Android多线程系列(一) AsyncTask基本使用以及源码解析
- .net开发者对android第二周的学习体会
- Android(安卓)自定义侧滑菜单
- Android(安卓)面试那些事之Java基础
- Android监听事件的回调机制
- 用android做的一个简单的短信发送器(当然不包括群发)
- 解决android程序中oncreate方法中调用百度地图MKSearchListener