Android系統定製的導航側邊欄
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鍵、最近工作列功能。當然,大家也可以自定義其他按鍵,實現酷炫的側邊欄。