1. 程式人生 > >Android系統定製的導航側邊欄

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