Подтвердить что ты не робот

Проблемы с экранами предпочтений с двумя панелями

Проблема
Поворот устройства из одноуровневого портрета PreferenceScreen в двухпанельный ландшафт PreferenceScreen, при этом ландшафт будет отображаться только как однослойный. Не происходит при просмотре экрана заголовков.

Настройка
Это касается только ICS и только. У меня есть PreferenceActivity, который загружает preference-headers. Каждый заголовок соединяется с Fragment, который, в свою очередь, загружает PreferenceScreen. Довольно бег mil.

Подробнее
Все работало хорошо, пока я не заметил, что Android только автоматически переключится на двухпанельный вид определенных экранов. После некоторых исследований я узнал из сообщения Commonsware, что Android будет делать это только для sw720dp. Немного отходов, если вы спросите меня, так как многие устройства Def имеют достаточно места для двух панелей. Поэтому я переопределил метод onIsMultiPane(), чтобы вернуть true для w600dp и выше. Работал как очарование.... любопытное.

Учитывая устройство, которое будет показывать однослойное изображение в портретном и двойном окне в ландшафте; просмотр заголовков в портрете и поворот на пейзаж, прекрасно работает. Однако, если вы выбираете заголовок и загружаете его последующий экран в портретном режиме, затем поворачивайте в альбомное положение, а устройство останется на одной панели вместо того, чтобы переключиться обратно в двойную панель. Если вы затем перейдете к экрану заголовков, он вернется к двойному окну, за исключением того, что он не будет предварительно выбирать заголовок. В результате подробная панель остается пустой.

Является ли это предполагаемым поведением? В любом случае, чтобы обойти это? Я попытался переопределить onIsHidingHeaders(), но это просто заставило все показать пустой экран.

Код
Предпочтение:

public class SettingsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
    super.onBuildHeaders(target);
    loadHeadersFromResource(R.xml.preference, target);
}

@Override
public boolean onIsMultiPane() {
    return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}
}


Заголовок предпочтения Frag:

public class ExpansionsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.pref_expansions);
}

public static ExpansionsFragment newInstance() {
    ExpansionsFragment frag = new ExpansionsFragment();

    return frag;
}
}
4b9b3361

Ответ 1

Проблема решена
Насколько популярным стал этот вопрос, я решил снова вернуться к этой проблеме и посмотреть, смогу ли найти разрешение... и я это сделал. Нашел неплохую работу, которая разрешает показ одиночной панели вместо двойной панели и гарантирует, что заголовок всегда будет предварительно выбран в режиме двойной панели.

Если вам неинтересно объяснение, вы можете просто перейти к коду. Если вам неинтересно ICS, многие из кода отслеживания заголовков могут быть удалены, так как JB добавил геттер для списка массивов заголовков.

Проблема с двойной панелью
При просмотре списка заголовков предпочтений в режиме одиночной панели или двойного окна создается только одна функция PreferenceActivity, и для обоих случаев она одинаковая. В результате никогда не возникает проблем при обработке поворотов экрана, которые переключают режим панели.

Однако в режиме одиночной панели, когда вы нажимаете на заголовок, соответствующий фрагмент присоединяется к NEW PreferenceActivity. Этот новый фрагмент, содержащий PreferenceActivity, никогда не вызывает onBuildHeaders(). И почему? Его не нужно отображать. В этом проблема.

При вращении этого фрагмента в режим двойной панели он не имеет никакого списка заголовков, чтобы показать, что он просто продолжает показывать только фрагмент. Даже если он показывает список заголовков, у вас будут проблемы с backstack, так как теперь у вас будет две копии заголовков PreferenceActivity. Продолжайте нажимать достаточно заголовков, и вы получите довольно большой стек действий, чтобы пользователь мог вернуться назад. В результате ответ прост. Просто finish() активность. Затем он загрузит исходную PreferenceActivity, которая имеет список заголовков и будет правильно отображать режим двойной панели.

Автоматический выбор заголовка
Следующий вопрос, требующий решения, заключался в том, что переключение между режимами с одиночной и двойной панелью с новым исправлением не автоматически выбирает заголовок. У вас остался список заголовков и загружен фрагмент деталей. Это исправление не так просто. В основном вам просто нужно следить за тем, какой последний заголовок был последним нажат, и обеспечить во время создания PreferenceActivity... заголовок всегда выбирается.

Это заканчивается тем, что немного раздражает в ICS, поскольку API не раскрывает геттер для списка отслеживаемых внутренними гусеницами. Android уже сохраняет этот список, и вы можете технически получить его, используя тот же закрытый встроенный строковый ключ, однако это просто плохой выбор дизайна. Вместо этого я предлагаю вручную снова сохранить его снова.

Если вам неинтересно ICS, вы можете просто использовать метод getHeaders(), открытый в JB, и не беспокоиться об этом состоянии состояния сохраненного/восстановленного.

Код

public class SettingsActivity extends PreferenceActivity {
private static final String STATE_CUR_HEADER_POS = "Current Position";
private static final String STATE_HEADERS_LIST   = "Headers List";

private int mCurPos = AdapterView.INVALID_POSITION;  //Manually track selected header position for dual pane mode
private ArrayList<Header> mHeaders;  //Manually track headers so we can select one. Required to support ICS.  Otherwise JB exposes a getter instead.

@Override
public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preference, target);
    mHeaders = (ArrayList<Header>) target;  //Grab a ref of the headers list
}

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

    //This is the only code required for ensuring a dual pane mode shows after rotation of a single paned preference screen
    if (onIsMultiPane() && onIsHidingHeaders()) {
        finish();
    }
}

@Override
public boolean onIsMultiPane() {
    //Override this if you want dual pane to show up on smaller screens
    return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);

    //Intercept a header click event to record its position.
    mCurPos = position;
}

@Override
protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);

    //Retrieve our saved header list and last clicked position and ensure we switch to the proper header.
    mHeaders = state.getParcelableArrayList(STATE_HEADERS_LIST);
    mCurPos = state.getInt(STATE_CUR_HEADER_POS);
    if (mHeaders != null) {
        if (mCurPos != AdapterView.INVALID_POSITION) {
            switchToHeader(mHeaders.get(mCurPos));
        } else {
            switchToHeader(onGetInitialHeader());
        }
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Persist our list and last clicked position
    if (mHeaders != null && mHeaders.size() > 0) {
        outState.putInt(STATE_CUR_HEADER_POS, mCurPos);
        outState.putParcelableArrayList(STATE_HEADERS_LIST, mHeaders);
    }
}
}

Ответ 2

Основная идея этого кода приведена в блоге Commonsware, связанном с вопросом, поэтому он считает актуальным. Я специально должен был расширить концепцию для решения проблемы изменения ориентации, которая звучит очень похоже на вопрос в вопросе, поэтому здесь надеемся, что это даст вам начало.

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

В моем комментарии к коду, посмотрите, поможет ли вызов checkNeedsResource в onCreate:

public class SettingsActivity
extends 
    PreferenceActivity
{

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

    // Show settings without headers for single pane or pre-Honeycomb. Make sure to check the
    // single pane or pre-Honeycomb condition again after orientation change.
    if (checkNeedsResource()) {
        MyApp app = (MyApp)getApplication();
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
        Settings settings = new Settings();
        addPreferencesFromResource(R.xml.prefs_api);
        settings.setupPreference(findPreference(MyApp.KEY_USERNAME), prefs.getString(MyApp.KEY_USERNAME, null), true);
        settings.setupPreference(findPreference(MyApp.KEY_API_URL_ROOT), prefs.getString(MyApp.KEY_API_URL_ROOT, null), true);
        if (this.isHoneycomb) {
            // Do not delete this. We may yet have settings that only apply to Honeycomb or higher.
            //addPreferencesFromResource(R.xml.prefs_general);
        }
        addPreferencesFromResource(R.xml.prefs_about);
        settings.setupPreference(findPreference(MyApp.KEY_VERSION_NAME), app.getVersionName());
    }
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
    super.onBuildHeaders(target);

    // This check will enable showing settings without headers for single pane or pre-Honeycomb. 
    if (!checkNeedsResource()) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }
}

private boolean checkNeedsResource() {
    // This check will enable showing settings without headers for single pane or pre-Honeycomb. 
    return (!this.isHoneycomb || onIsHidingHeaders() || !onIsMultiPane());
}

private boolean isHoneycomb = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB);

}

public class Settings {

public Settings() {
}

public void setupPreference(Preference pref, String summary, boolean setChangeListener) {
    if (pref != null) {
        if (summary != null) {
            pref.setSummary(summary);
        }

        pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {

            @Override
            public boolean onPreferenceChange(Preference pref, Object newValue) {
                pref.setSummary(newValue.toString());
                return true;
            }

        });
    }
}

public void setupPreference(Preference pref, String summary) {
    setupPreference(pref, summary, false);
}

}