双窗格首选项屏幕出现问题

问题
将设备从单窗格纵向PreferenceScreen旋转到双窗格横向PreferenceScreen ,导致横向仅显示为单窗格。 查看标题屏幕时不会发生。

build立
这仅适用于ICS。 我有一个PreferenceActivity加载preference-headers 。 每个标题链接一个Fragment ,然后加载一个PreferenceScreen 。 密尔的奔跑。

细节
一切运作良好,直到我注意到Android将只能自动切换到某个屏幕的双窗格外观。 经过一番研究,我从Commonsware的post中了解到,Android只会为sw720dp做这个工作。 如果你问我这个问题,那么很多设备都没有足够的空间来放置两个窗格。 所以我重写了onIsMultiPane()方法返回true和w600dp。 工作就像一个魅力….还挺。

给定一个设备,它将在纵向上显示单个窗格,在横向上显示双个窗格。 在纵向查看标题和旋转到横向,工作正常。 但是,如果select标题并在纵向模式下加载后续屏幕,则旋转横向设备将保持单窗格,而不是切换回双窗格。 如果您然后返回到标题屏幕,它将返回到双窗格外观,除了它不会预先select标题。 结果是详细的窗格保持空白。

这是预期的行为? 无论如何解决它? 我试着重写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; } } 

问题解决了
随着这个问题变得如此受欢迎,我决定再次重新讨论这个问题,看看我能否find一个解决scheme……而且我也这样做了。 find一个很好的解决scheme,解决了单窗格而不是双窗格,并确保在双窗格模式下始终预选标题。

如果你不关心解释,你可以跳到代码。 如果你不关心ICS,那么当JB为headers数组列表添加一个getter时,可以删除大量的头部跟踪代码。

双窗格问题
在单窗格模式或双窗格模式下查看首选项标题列表时,只会创build一个PreferenceActivity,这两种情况下的活动都是相同的。 因此,在处理屏幕旋转时将不会出现问题,从而切换窗格模式。

但是,在单窗格模式下,当您单击标题时,相应的片段将附加到NEW PreferenceActivity。 这个包含PreferenceActivity的新片段永远不会调用onBuildHeaders() 。 为什么呢? 它不需要显示它们。 这是谎言的问题。

当将片段旋转到双窗格模式时,它没有任何头部列表显示,所以它只是继续显示片段。 即使它确实显示了标题列表,也会有一些叠后问题,因为现在您将有两个显示标题的PreferenceActivity副本。 保持点击足够的标题,你会得到相当长的一堆活动供用户浏览。 结果,答案很简单。 刚刚finish()活动。 然后,它会加载原始PreferenceActivity,它具有标题列表,并将正确显示双窗格模式。

自动select标题
需要解决的下一个问题是,使用新修复程序在单窗格模式与双窗格模式之间切换时,不会自动select标题。 您留下了一个标题列表,没有加载任何细节片段。 这个修复不是那么简单。 基本上,你只需要跟踪哪个头被最后点击,并确保在创buildPreferenceActivity期间…总是select一个头。

这最终在ICS中有点烦人,因为API不公开内部跟踪头列表的getter。 Android已经坚持这个列表,你可以通过使用相同的私有存储的内部string键在技术上检索它,但这只是一个糟糕的deviseselect。 相反,我build议你自己手动坚持下去。

如果你不关心ICS,那么你可以使用在JB中公开的getHeaders()方法,而不用担心任何这种保存/恢复状态的东西。

 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博客条目链接在这个问题上,所以感觉相关。 我特别要扩展这个概念来处理一个与问题中听起来非常相似的方向改变问题,所以这里希望能给你一个开始。

设置类不应该对方向问题有任何影响,但无论如何要清楚。

根据我的代码评论,看看onCreatecheckNeedsResource调用是否有帮助:

 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); } 

}