Обновлено до AppCompat v22.1.0 и теперь onKeyDown и onKeyUp не запускаются при нажатии клавиши меню

Я только что обновил свое приложение, чтобы использовать недавно выпущенный App2Compat v22.1.0, а теперь onKeyDown и onKeyUp не запускаются при нажатии клавиши меню. Другие клавиши правильно запускают onKeyDown и onKeyUp, но когда я нажимаю клавишу меню, ничего не происходит. Если я откажу до версии v22.0.0, все вернется к правильной работе.

Как это исправить?

Ответы

Ответ 1

Обновление 23 августа

Этот был исправлен в v23.0.0 библиотеки поддержки appcompat-v7. Обновите последнюю версию, чтобы увидеть это исправлено.


Обновление 19 июля

К сожалению, AppCompat v22.2.1 снова разбил события onKeyDown и onKeyUp . Я только что обновил AppCompatActivityMenuKeyInterceptor для поддержки v22.1.x, а также v22.2.1


Обновление 29 мая

Этот был исправлен в библиотеке поддержки appcompat-v7 v22.2.0. Обновите последнюю версию, чтобы увидеть это исправлено.


К сожалению, AppCompat v22.1.0 перехватывает события onKeyDown и onKeyUp и не распространяет их при нажатии клавиши меню. Единственное возможное решение включает использование Reflection для перехвата событий onKeyDown и onKeyUp до AppCompat.

Добавьте этот класс в свой проект:

public class AppCompatActivityMenuKeyInterceptor {

    private static final String FIELD_NAME_DELEGATE = "mDelegate";
    private static final String FIELD_NAME_WINDOW = "mWindow";

    public static void intercept(AppCompatActivity appCompatActivity) {
        new AppCompatActivityMenuKeyInterceptor(appCompatActivity);
    }

    private AppCompatActivityMenuKeyInterceptor(AppCompatActivity activity) {
        try {
            Field mDelegateField = AppCompatActivity.class.getDeclaredField(FIELD_NAME_DELEGATE);
            mDelegateField.setAccessible(true);
            Object mDelegate = mDelegateField.get(activity);

            Class mDelegateClass = mDelegate.getClass().getSuperclass();
            Field mWindowField = null;

            while (mDelegateClass != null) {
                try {
                    mWindowField = mDelegateClass.getDeclaredField(FIELD_NAME_WINDOW);
                    break;
                } catch (NoSuchFieldException ignored) {
                }

                mDelegateClass = mDelegateClass.getSuperclass();
            }

            if (mWindowField == null)
                throw new NoSuchFieldException(FIELD_NAME_WINDOW);

            mWindowField.setAccessible(true);
            Window mWindow = (Window) mWindowField.get(mDelegate);

            Window.Callback mOriginalWindowCallback = mWindow.getCallback();
            mWindow.setCallback(new AppCompatWindowCallbackCustom(mOriginalWindowCallback, activity));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private class AppCompatWindowCallbackCustom extends WindowCallbackWrapper {

        private WeakReference<AppCompatActivity> mActivityWeak;

        public AppCompatWindowCallbackCustom(Window.Callback wrapped, AppCompatActivity appCompatActivity) {
            super(wrapped);

            mActivityWeak = new WeakReference<AppCompatActivity>(appCompatActivity);
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            final int keyCode = event.getKeyCode();

            AppCompatActivity appCompatActivity = mActivityWeak.get();

            if (appCompatActivity != null && keyCode == KeyEvent.KEYCODE_MENU) {
                if (appCompatActivity.dispatchKeyEvent(event))
                    return true;
            }

            return super.dispatchKeyEvent(event);
        }
    }
}

Вызвать AppCompatActivityMenuKeyInterceptor.intercept(this) в onCreate вашей деятельности:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Initialize the interceptor
        AppCompatActivityMenuKeyInterceptor.intercept(this);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Now onKeyDown is called also for KEYCODE_MENU
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            //do your stuff

            //return false if you want to propagate the
            //KeyEvent to AppCompat, return true otherwise
            return false;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        // Now onKeyUp is called also for KEYCODE_MENU
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            //do your stuff

            //return false if you want to propagate the
            //KeyEvent to AppCompat, return true otherwise
            return false;
        }

        return super.onKeyUp(keyCode, event);
    }
}

Если вы используете ProGuard или DexGuard, добавьте эти правила в свою конфигурацию:

-keepclassmembers class android.support.v7.app.AppCompatActivity {
    private android.support.v7.app.AppCompatDelegate mDelegate;
}

-keepclassmembers class android.support.v7.app.AppCompatDelegateImplBase {
    final android.view.Window mWindow;
}

Теперь ваша активность может принимать событие onKeyDown и onKeyUp также для клавиши меню.

Ответ 2

Вместо onKeyUp() или onKeyDown() можно просто использовать dispatchKeyEvent(). Посмотрите на следующий код android-developers.blogspot.com.

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        if (event.getAction() == KeyEvent.ACTION_DOWN
                && event.getRepeatCount() == 0) {

            // Tell the framework to start tracking this event.
            getKeyDispatcherState().startTracking(event, this);
            return true;

        } else if (event.getAction() == KeyEvent.ACTION_UP) {
            getKeyDispatcherState().handleUpEvent(event);
            if (event.isTracking() && !event.isCanceled()) {
                // DO BACK ACTION HERE
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    } else {
        return super.dispatchKeyEvent(event);
    }
}