PreferenceFragment не может прокручиваться вверх / вниз на XE16 (отлично работает на XE12)

У меня есть несколько пользовательских настроек, в основном простые флажки, в моем приложении Glass GDK. Мне не удалось найти парадигму предпочтений, специфичных для стекла, поэтому я использовал PreferenceFragment, и он отлично работал на XE12.

К вашему сведению: когда я реализовал его, он изначально выглядел плохо, но я улучшил его, используя следующий стиль в AndroidManifest для своего SettingsActivity:

<style name="Theme.Preferences" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen" />

У меня не было возможности не обновляться до XE16 (кроме отключения сетевого подключения). После обновления я скорректировал использование API моего приложения с учетом нескольких изменений XE16. В основном все работало.

Первое, что я заметил, это то, что при перемещении вниз в MainActivity моего погружения больше не вернется к Live Card. Я исправил это, заставив мой обработчик MainActivity onGesture вернуть false для Gesture.SWIPE_DOWN.

Второе, что я заметил, - это цель этого вопроса: My SettingsActivity, который обертывает PreferenceFragment, больше не позволяет мне перемещаться вверх и вниз по списку предпочтений, проводя пальцем влево и вправо. Мой код находится в конце этого поста. Я добавил GestureDetector, чтобы помочь отладить эту проблему после того, как она была замечена. Я вижу, что SWIPE_LEFT и SWIPE_RIGHT регистрируются, но независимо от того, что я возвращаю, или даже если я удаляю код жеста, выбор списка предпочтений никогда не перемещается с первого элемента. Первый элемент - это CheckBoxPreference, который выполняет переключение, когда я нажимаю.

Я видел несколько других приложений Glass, которые используют настройки Android (PreferenceActivity или PreferenceFragment), и все они, похоже, теперь не работают.

Как правильно реализовать настройки на Glass или как заставить работать PreferenceFragment?

public class SettingsActivity //
                extends Activity //
                implements GestureDetector.BaseListener
{
    private static final String TAG             = WtcLog.TAG(SettingsActivity.class);

    public static final int     RESULT_SIGN_OUT = RESULT_FIRST_USER + 1;

    private static final String TAG_PREFERENCES = "preferences";

    private GestureDetector     mGestureDetector;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        mGestureDetector = new GestureDetector(this);
        mGestureDetector.setBaseListener(this);

        getFragmentManager() //
        .beginTransaction() //
        .replace(android.R.id.content, new FragmentSettings(), TAG_PREFERENCES) //
        .commit();
    }

    public static class FragmentSettings extends PreferenceFragment
    {
        private ApplicationGlass   mApplication;
        private WavePreferences    mPreferences;

        private PreferenceScreen   mScreenTop;
        private PreferenceCategory mCategoryDebug;

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            Activity activity = getActivity();
            mApplication = (ApplicationGlass) activity.getApplication();
            mPreferences = mApplication.getPreferences();

            addPreferencesFromResource(R.xml.preferences);

            mScreenTop = (PreferenceScreen) findPreference("screen_top");

            //
            // Remember Password
            //
            CheckBoxPreference prefRememberPassword = (CheckBoxPreference) findPreference("pref_remember_password");
            prefRememberPassword.setChecked(mPreferences.getRememberPassword());
            prefRememberPassword.setOnPreferenceChangeListener(new OnPreferenceChangeListener()
            {
                @Override
                public boolean onPreferenceChange(Preference preference, Object newValue)
                {
                    boolean rememberPassword = (Boolean) newValue;

                    String password = null;
                    if (rememberPassword)
                    {
                        password = mApplication.getSessionManager().getLastStartConnectInfo().getPassword();
                    }

                    mPreferences.setRememberPassword(rememberPassword);
                    mPreferences.setPassword(password);

                    return true;
                }
            });

            ...
        }
    }

    @Override
    public boolean onGenericMotionEvent(MotionEvent event)
    {
        if (mGestureDetector != null)
        {
            return mGestureDetector.onMotionEvent(event);
        }
        return false;
    }

    @Override
    public boolean onGesture(Gesture gesture)
    {
        WtcLog.debug(TAG, "onGesture(" + gesture + ")");
        switch (gesture)
        {
            case LONG_PRESS:
                WtcLog.debug(TAG, "onGesture: LONG_PRESS");
                break;
            case TAP:
                WtcLog.debug(TAG, "onGesture: TAP");
                break;
            case TWO_TAP:
                WtcLog.debug(TAG, "onGesture: TWO_TAP");
                break;
            case SWIPE_RIGHT:
                WtcLog.debug(TAG, "onGesture: SWIPE_RIGHT");
                break;
            case SWIPE_LEFT:
                WtcLog.debug(TAG, "onGesture: SWIPE_LEFT");
                break;
            case SWIPE_DOWN:
                WtcLog.debug(TAG, "onGesture: SWIPE_DOWN");
                break;
            case SWIPE_UP:
                WtcLog.debug(TAG, "onGesture: SWIPE_UP");
                break;
            case THREE_LONG_PRESS:
                WtcLog.debug(TAG, "onGesture: THREE_LONG_PRESS");
                break;
            case THREE_TAP:
                WtcLog.debug(TAG, "onGesture: THREE_TAP");
                break;
            case TWO_LONG_PRESS:
                WtcLog.debug(TAG, "onGesture: TWO_LONG_PRESS");
                break;
            case TWO_SWIPE_DOWN:
                WtcLog.debug(TAG, "onGesture: TWO_SWIPE_DOWN");
                break;
            case TWO_SWIPE_LEFT:
                WtcLog.debug(TAG, "onGesture: TWO_SWIPE_LEFT");
                break;
            case TWO_SWIPE_RIGHT:
                WtcLog.debug(TAG, "onGesture: TWO_SWIPE_RIGHT");
                break;
            case TWO_SWIPE_UP:
                WtcLog.debug(TAG, "onGesture: TWO_SWIPE_UP");
                break;
            default:
                WtcLog.error(TAG, "onGesture: unknown gesture \"" + gesture + "\"");
                break;
        }
        return false;
    }
}

person swooby    schedule 18.04.2014    source источник
comment
Я написал здесь о неработающей ListView прокрутке на XE16 и 16.1 - code.google.com/p/google-glass-api/issues/detail?id=484   -  person Sean Barbeau    schedule 22.04.2014


Ответы (3)


Я думаю, это связано с тем, что listViews больше не прокручиваются правильно в XE16. Я ответил здесь о том, как вернуться назад: https://stackoverflow.com/a/23146305/1114876.

По сути, в операторе switch / case для жестов вызовите

myListView.setSelection(myListView.getSelectedItemPosition()+1); для перехода вперед по списку и myListView.setSelection(myListView.getSelectedItemPosition()-1); для перехода назад.

изменить: Вот некоторая информация о том, как PreferenceActivity наследует класс ListActivity: https://stackoverflow.com/a/8432096/1114876 Вы можете получить список PreferenceActivity, вызвав getListView(). Затем прикрепите распознаватель жестов.

person adamup    schedule 19.04.2014
comment
Предварительный +1 за то, что был так полезен. Я приму ответ после небольшого расследования. Да, я заметил твой пост и все равно использовал его, чтобы пойти по этому пути. Я дам знать, что узнаю ... - person swooby; 19.04.2014
comment
Спасибо! Очень любопытно узнать, что изменилось с обновлением XE16. Сообщите мне, как продвигается ваш поиск. - person adamup; 19.04.2014
comment
Хммм ... GestureDetector, похоже, не работает для меня с ListActivity (события Gesture не запускаются). Может, эта проблема мешает? code.google.com/p/google-glass- api / issues / detail? id = 401 @swooby - У вас сработал GestureDetector? - person Sean Barbeau; 23.04.2014
comment
Ах, похоже, вам нужно переопределить onGenericMotionEvent (событие MotionEvent) и передать их в GestureDetector, как это сделал @swooby в своем вопросе. Теперь это работает, хотя всегда сохраняет выбранный элемент как верхний элемент в списке (то есть прокручивается весь список). Есть ли способ имитировать поведение по умолчанию, когда следующий элемент не появляется на экране, пока вы не дойдете до конца списка? - person Sean Barbeau; 23.04.2014
comment
Мне не удалось заставить это работать для PreferenceFragment, потому что они @hide аннотировали getListView (): github.com/android/platform_frameworks_base/blob/master/core/ Не повезло, чтобы это работало и в PreferenceActivity. - person swooby; 24.04.2014

События клавиатуры (dpad) по-прежнему правильно обрабатываются ListView и PreferenceFragment на XE16, поэтому я придумал обходной путь, превратив события движения стеклянной сенсорной панели в события движения клавиатуры.

Ниже я добавил реализацию экземпляра PreferenceFragment временного решения, который автоматически обрабатывает преобразование. Полную реализацию и использование можно найти по адресу http://github.com/ne0fhyk/AR-Glass/blob/master/src/com/ne0fhyklabs/freeflight/fragments/GlassPreferenceFragment.java.

В приведенной ниже реализации стакан GestureDetector обрабатывает полученные жесты, отправляя совпадающее событие клавиши DPAD текущему экземпляру Window.Callback.

Класс структурирован таким образом, что вы можете просто поместить его в свой проект и изменить реализацию PreferenceFragment, чтобы расширить его, вместо того, чтобы он снова работал.

/**
 * This class provides the necessary workarounds to make the default preference fragment screen
 * work again on glass post XE16.
 */
public class GlassPreferenceFragment extends PreferenceFragment {

    @Override
    public void onStart(){
        super.onStart();

        final Activity parentActivity = getActivity();
        if(parentActivity != null){
            updateWindowCallback(parentActivity.getWindow());
        }
    }

    @Override
    public void onStop(){
        super.onStop();

        final Activity parentActivity = getActivity();
        if(parentActivity != null){
            restoreWindowCallback(parentActivity.getWindow());
        }
    }

    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference){
        if(preference instanceof PreferenceScreen){
            //Update the preference dialog window callback. The new callback is able to detect
            // and handle the glass touchpad gestures.
            final Dialog prefDialog = ((PreferenceScreen) preference).getDialog();
            if(prefDialog != null) {
                updateWindowCallback(prefDialog.getWindow());
            }
        }

        return super.onPreferenceTreeClick(preferenceScreen, preference);
    }

    /**
     * Replace the current window callback with one supporting glass gesture events.
     * @param window
     */
    private void updateWindowCallback(Window window){
        if(window == null) {
            return;
        }

        final Window.Callback originalCb = window.getCallback();
        if(!(originalCb instanceof GlassCallback)) {
            final GlassCallback glassCb = new GlassCallback(window.getContext(), originalCb);
            window.setCallback(glassCb);
        }
    }

    /**
     * Restore the original window callback for this window, if it was updated with a glass
     * window callback.
     * @param window
     */
    private void restoreWindowCallback(Window window){
        if(window == null){
            return;
        }

        final Window.Callback currentCb = window.getCallback();
        if(currentCb instanceof GlassCallback){
            final Window.Callback originalCb = ((GlassCallback)currentCb).getOriginalCallback();
            window.setCallback(originalCb);
        }
    }

    /**
     * Window.Callback implementation able to detect, and handle glass touchpad gestures.
     */
    private static class GlassCallback implements Window.Callback {

        /**
         * Used to detect and handle glass touchpad events.
         */
        private final GestureDetector mGlassDetector;

        /**
         * This handles the motion events not supported by the glass window callback.
         */
        private final Window.Callback mOriginalCb;

        public GlassCallback(Context context, Window.Callback original){
            mOriginalCb = original;

            mGlassDetector = new GestureDetector(context);
            mGlassDetector.setBaseListener(new GestureDetector.BaseListener() {
                @Override
                public boolean onGesture(Gesture gesture) {
                    switch(gesture){
                        case TAP:
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                                    KeyEvent.KEYCODE_DPAD_CENTER));
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                                    KeyEvent.KEYCODE_DPAD_CENTER));
                            return true;

                        case SWIPE_LEFT:
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                                    KeyEvent.KEYCODE_DPAD_UP));
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                                    KeyEvent.KEYCODE_DPAD_UP));
                            return true;

                        case SWIPE_RIGHT:
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                                    KeyEvent.KEYCODE_DPAD_DOWN));
                            dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                                    KeyEvent.KEYCODE_DPAD_DOWN));
                            return true;
                    }

                    return false;
                }
            });
        }

        /**
         * @return the Window.Callback instance this one replaced.
         */
        public Window.Callback getOriginalCallback(){
            return mOriginalCb;
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            return mOriginalCb.dispatchKeyEvent(event);
        }

        @Override
        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
            return mOriginalCb.dispatchKeyShortcutEvent(event);
        }

        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            return mOriginalCb.dispatchTouchEvent(event);
        }

        @Override
        public boolean dispatchTrackballEvent(MotionEvent event) {
            return mOriginalCb.dispatchTrackballEvent(event);
        }

        @Override
        public boolean dispatchGenericMotionEvent(MotionEvent event) {
            return mGlassDetector.onMotionEvent(event) || mOriginalCb.dispatchGenericMotionEvent
                    (event);
        }

        @Override
        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
            return mOriginalCb.dispatchPopulateAccessibilityEvent(event);
        }

        @Nullable
        @Override
        public View onCreatePanelView(int featureId) {
            return mOriginalCb.onCreatePanelView(featureId);
        }

        @Override
        public boolean onCreatePanelMenu(int featureId, Menu menu) {
            return mOriginalCb.onCreatePanelMenu(featureId, menu);
        }

        @Override
        public boolean onPreparePanel(int featureId, View view, Menu menu) {
            return mOriginalCb.onPreparePanel(featureId, view, menu);
        }

        @Override
        public boolean onMenuOpened(int featureId, Menu menu) {
            return mOriginalCb.onMenuOpened(featureId, menu);
        }

        @Override
        public boolean onMenuItemSelected(int featureId, MenuItem item) {
            return mOriginalCb.onMenuItemSelected(featureId, item);
        }

        @Override
        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
            mOriginalCb.onWindowAttributesChanged(attrs);
        }

        @Override
        public void onContentChanged() {
            mOriginalCb.onContentChanged();
        }

        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            mOriginalCb.onWindowFocusChanged(hasFocus);
        }

        @Override
        public void onAttachedToWindow() {
            mOriginalCb.onAttachedToWindow();
        }

        @Override
        public void onDetachedFromWindow() {
            mOriginalCb.onDetachedFromWindow();
        }

        @Override
        public void onPanelClosed(int featureId, Menu menu) {
            mOriginalCb.onPanelClosed(featureId, menu);
        }

        @Override
        public boolean onSearchRequested() {
            return mOriginalCb.onSearchRequested();
        }

        @Nullable
        @Override
        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
            return mOriginalCb.onWindowStartingActionMode(callback);
        }

        @Override
        public void onActionModeStarted(ActionMode mode) {
            mOriginalCb.onActionModeStarted(mode);
        }

        @Override
        public void onActionModeFinished(ActionMode mode) {
            mOriginalCb.onActionModeFinished(mode);
        }
    }
}
person ne0fhyk    schedule 30.04.2014
comment
КЛАССНО! Я попробую в следующий раз, когда буду в этом коде! - person swooby; 05.05.2014

К сожалению, официальный ответ Google заключается в том, что ListViews (или любой объект пользовательского интерфейса, использующий ListViews) не следует использовать в Glass. Из https://code.google.com/p/google-glass-api/issues/detail?id=484#c6:

Из-за того, что модель взаимодействия с пользователем на Glass сильно отличается от других устройств Android, существует ряд стандартных виджетов, которые не удастся легко использовать на Glass. ListView - один из них. Вместо того, чтобы пытаться обойти эти проблемы, чтобы использовать виджет, который предоставит вашим пользователям плохой UX, вам следует перенести свой код на использование CardScrollView в GDK.

ListView указан как поддерживаемый на странице LiveCard, поэтому это несколько противоречиво: https://developers.google.com/glass/develop/gdk/live-cards

Я понимаю, что мы не хотим показывать длинные списки пользователям в Glass, но у IMO есть варианты использования (мой - информация о транспорте в реальном времени - см. этот комментарий, а также список" ok, glass ... "), когда у вас может быть еще один или два элементов, чем то, что вы можете уместить на экране, и имеет смысл прокручивать по вертикали, а не принудительно каскадировать на другую карту.

Я попросил пояснить передовые методы работы с вертикальными списками в Google Glass здесь.

person Sean Barbeau    schedule 23.04.2014
comment
Я полностью согласен с вашим комментарием и считаю, что предпочтения - это еще одна область, в которой список является разумным и уместным. - person swooby; 24.04.2014