背景介绍

最近接到一个需求。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初始化流程,有兴趣的小伙伴可以自己尝试跟一下源码。

更多相关文章

  1. Android中Button点击事件实现的三种方式总结及Demo演示
  2. "必须搭配使用google play服务才能运行"或“您必须先更新Google
  3. Android多线程系列(一) AsyncTask基本使用以及源码解析
  4. .net开发者对android第二周的学习体会
  5. Android(安卓)自定义侧滑菜单
  6. Android(安卓)面试那些事之Java基础
  7. Android监听事件的回调机制
  8. 用android做的一个简单的短信发送器(当然不包括群发)
  9. 解决android程序中oncreate方法中调用百度地图MKSearchListener

随机推荐

  1. Android线程的一些问题
  2. 在Eclipse中加入Android源码
  3. 【腾讯开源】Android性能测试工具APT使用
  4. android用贝塞尔曲线完成viewpager轮播指
  5. Git | 上传 Android(安卓)项目到 GitHub
  6. Android(安卓)- 分享内容 - 给其他APP发
  7. Android(安卓)使用 Usb Accessory 模式与
  8. 一个Android应用的汉化的技术
  9. android 调用系统相册选取照片或者打开相
  10. Android(安卓)事件传递流程 - 基于WMS、I