Проблемы с экранами предпочтений с двумя панелями
Задача
При повороте устройства из однопанельного портрета PreferenceScreen
в двухпанельный ландшафт PreferenceScreen
ландшафт будет отображаться только как однопанельный. Не возникает при просмотре экрана заголовков.
Setup
Это только для ICS и up. У меня есть PreferenceActivity
, который загружает preference-headers
. Каждый заголовок связывается с A Fragment
, который, в свою очередь, загружает a PreferenceScreen
. Довольно далеко от Мила.
Подробности
Все работало хорошо, пока я не заметил, что Андроид будет только автоматическое переключение на двухпанельный поиск определенных экранов. После некоторых исследований я узнал из сообщения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);
}
}
Фрагмент Заголовка Предпочтения:
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;
}
}
2 ответа:
Проблема Решена
Учитывая, насколько популярным стал этот вопрос, я решил вернуться к нему еще раз и посмотреть, смогу ли я найти решение...и я это сделал. Нашел хороший небольшой обходной путь, который решает отображение одной панели вместо двойной панели и гарантирует, что заголовок всегда предварительно выбран, когда в режиме двойной панели.Если вас не волнует объяснение, вы можете просто перейти к коду. Если вы не заботитесь о ICS, большая часть кода отслеживания заголовка может быть удалена как JB добавил геттер для списка массивов заголовков.
Проблема Двойной Панели
При просмотре списка заголовков предпочтений в режиме одной панели или режиме двух панелей создается только одно действие предпочтения, и это одно и то же действие для обоих случаев. В результате никогда не возникает проблем с обработкой поворотов экрана, которые переключают режим панели.Однако в режиме одной панели при щелчке по заголовку соответствующий фрагмент присоединяется к новому действию 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); } } }
Ключевая идея, лежащая в основе приведенного ниже кода, пришла из записи блога 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); }
}