双窗格首选项屏幕的问题

Jay*_*yer 36 java android preferenceactivity android-preferences android-fragments

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

设置
仅适用于ICS及以上版本.我有一个PreferenceActivity负载preference-headers.每个标题链接一个Fragment,然后加载一个PreferenceScreen.相当运行的mil.

细节
一切运作良好,直到我注意到Android只会自动切换到某个屏幕的双窗格外观.经过一些研究,我从一篇Commonsware帖子中了解到,Android只会针对sw720dp这样做.如果你问我我有点浪费,因为许多设备def有足够的空间用于双窗格.所以我重写了onIsMultiPane()为w600dp和up返回true 的方法.工作就像一个魅力......有点儿.

给定一个设备,将在纵向和双窗格中显示单窗格; 以纵向查看标题并旋转到横向,工作正常.但是,如果选择一个标题并以纵向模式加载它的后续屏幕,则旋转到横向设备将保持单窗格而不是切换回双窗格.如果您然后返回导航到标题屏幕,它将返回双窗格外观,但它不会预先选择标题.因此,详细窗格保持空白.

这是预期的行为吗?无论如何要解决它吗?我也试过覆盖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);
}
}
Run Code Online (Sandbox Code Playgroud)


首选项标题片段:

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;
}
}
Run Code Online (Sandbox Code Playgroud)

Jay*_*yer 2

问题已解决
随着这个问题变得如此受欢迎,我决定再次重新审视这个问题,看看是否能找到解决方案……我做到了。找到了一个很好的小解决方案,可以解决显示单窗格而不是双窗格的问题,并确保在双窗格模式下始终预先选择标题。

如果您不关心解释,可以直接跳到代码。如果您不关心 ICS,则可以删除许多标头跟踪代码,因为 JB 为标头数组列表添加了 getter。

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

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

当将该片段旋转到双窗格模式时,它没有任何要显示的标题列表,因此它只是继续仅显示该片段。即使它确实显示了标头的列表,您也会遇到一些返回堆栈问题,因为您现在将有两个显示标头的 PreferenceActivity 副本。继续单击足够多的标题,您将获得相当长的活动堆栈,供用户导航回来。结果,答案很简单。只是finish()活动。然后,它将加载原始的 PreferenceActivity,它具有标题列表,并将正确显示双窗格模式。

自动选择标题
需要解决的下一个问题是,使用新修复程序在单窗格模式与双窗格模式之间切换时不会自动选择标题。您只剩下一个标题列表,并且没有加载任何详细信息片段。这个修复并不那么简单。基本上,您只需跟踪最后单击的标头并确保在 PreferenceActivity 创建期间...始终选择标头。

这最终在 ICS 中有点烦人,因为 API 没有公开内部跟踪标头列表的 getter。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);
    }
}
}
Run Code Online (Sandbox Code Playgroud)