Android手机的导航栏一般都放在底部,导航按键包括返回键、home键、最近任务键。而有些Android设备希望把导航栏放在左右两边,也就是改成侧边栏,这时候就需要二次定制开发。首先,把原生的底部导航栏屏蔽掉。然后,通过WindowManager添加悬浮的侧边栏,组合按键除了返回键、home键、最近任务键,还可以自定义添加其他按键。
为了响应按键点击事件,我们需要重写自定义View的onTouchEvent方法,监听ACTION_DOWN、ACTION_MOVE、ACTION_UP等动作,然后注入inputEvent事件,让系统响应对应键值的点击事件。通过查看KeyEvent源码,我们可知back键值、home键值:
/** Key code constant: Home key.
* This key is handled by the framework and is never delivered to applications. */
public static final int KEYCODE_HOME = 3;
/** Key code constant: Back key. */
public static final int KEYCODE_BACK = 4;
注入inputEvent事件,需要构造KeyEvent对象,原方法是这样的:
/**
* Create a new key event.
*
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this event happened.
* @param action Action code: either {@link #ACTION_DOWN},
* {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
* @param code The key code.
* @param repeat A repeat count for down events (> 0 if this is after the
* initial down) or event count for multiple events.
* @param metaState Flags indicating which meta keys are currently pressed.
* @param deviceId The device ID that generated the key event.
* @param scancode Raw device scan code of the event.
* @param flags The flags for this key event
* @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
*/
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
int deviceId, int scancode, int flags, int source);
在onTouchEvent监听到Down、Up动作时,我们在这个时机注入:
void sendEvent(int action, int flags, long when) {
int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
//调用系统API注入inputEvent
InputUtil.injectInputEvent(ev);
}
系统API的注入inputEvent事件,只需要调用injectInputEvent方法:
InputManager.getInstance().injectInputEvent(ev,InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
而我们应用层API无法直接调用到,怎么办呢?大家应该都想起了反射,不错就是反射调用:
public static void injectInputEvent(KeyEvent event) {
try {
Class<InputManager> inputManagerClass = InputManager.class;
Method methodInject = inputManagerClass.getDeclaredMethod("injectInputEvent", InputEvent.class, int.class);
methodInject.setAccessible(true);
Method method = inputManagerClass.getDeclaredMethod("getInstance");
method.setAccessible(true);
InputManager instance = (InputManager) method.invoke(inputManagerClass);
methodInject.invoke(instance, event, 0);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
}
除了反射调用方法,还有另一种方法是,使用Instrumentation来发送按键事件。但是,需要在子线程执行,否则系统会抛异常:
public static void doInject(final KeyEvent event){
new Thread(new Runnable() {
@Override
public void run() {
Instrumentation instrumentation = new Instrumentation();
instrumentation.sendKeySync(event);
}
}).start();
}
与back键、home键的事件注入不同的是,最近任务栏需要启动系统的任务栏Activity。通过查看系统源码,我们可以任务栏的包名是com.android.systemui,对应的Activity完整路径是com.android.systemui.recents.RecentsActivity。知道这些信息,我们就可以启动任务栏Activty:
public void startRecentApp() {
Intent intent = new Intent("com.android.systemui.recents.TOGGLE_RECENTS");
ComponentName component = new ComponentName("com.android.systemui",
"com.android.systemui.recents.RecentsActivity");
intent.setComponent(component);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
至此,我们已经实现侧边栏的back键、home键、最近任务栏功能。当然,大家也可以自定义其他按键,实现酷炫的侧边栏。