双屏偏好设置屏幕存在的问题

36

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

设置
仅适用于ICS及以上版本。我有一个加载了 preference-headersPreferenceActivity。每个标题都链接到一个 Fragment,该 Fragment 再加载一个 PreferenceScreen。相当普通的东西。

细节
一切工作正常,直到我注意到 Android 只会自动切换到两个窗格外观的某些屏幕。经过一些研究,我从 Commonsware 帖子 中了解到,Android 只会在 sw720dp 上这样做。如果你问我,这有点浪费,因为许多设备确实有足够的空间容纳两个窗格。所以我重写了 onIsMultiPane() 方法,在 w600dp 及以上的情况下返回 true。 像魔法一样运作……有点。

对于一个在纵向显示单面并在横向显示双面的设备;在纵向查看标题然后旋转到横向,一切正常。 但是,如果在纵向模式下选择一个标题并加载其后续屏幕,然后旋转到横向,则设备将保持单窗格而不是切换回双窗格。 如果然后返回到标题屏幕,则它将返回到双窗格外观,但不会预选标题。 结果详细窗格保持空白。

这是意料之中的行为吗? 有办法解决吗? 我还尝试过重写 onIsHidingHeaders(),但那只会导致所有东西都显示为空白屏幕。

代码
Preference Activity:

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

你有一个包含状态“visible”的值。 - user2558337
你是否在清单文件中使用了android:configChanges="orientation|keyboardHidden"来避免活动的销毁和重建?如果是这样,那可能就是问题所在。我怀疑的原因是当你改变方向时,Android应该从头开始重建你的活动,但似乎这里并没有发生。这只是一个需要检查的问题。 - Glaucus
不,我没有覆盖配置更改。考虑到缺乏答案,我只选择像平常一样使用首选项。 - Ifrit
让我们查看这两个首选项文件的 XML。 - Technivorous
你试过关掉它再重新开启吗? - madmik3
显示剩余4条评论
2个回答

2
问题解决
由于这个问题变得越来越受欢迎,我决定重新审视这个问题,并尝试找到一个解决方案...我找到了。我找到了一个很好的小技巧,可以解决单面板显示而不是双面板并确保在双面板模式下始终预先选择头文件的问题。
如果您不关心解释,可以直接跳到代码部分。如果您不关心ICS,则可以删除大量标题跟踪代码,因为JB添加了一个用于header数组列表的getter。 双面板问题
在单面板模式或双面板模式下查看首选项标题列表时,只创建一个PreferenceActivity,两种情况下都是同一个活动。因此,在处理会切换窗格模式的屏幕旋转时,永远不会出现问题。
然而,在单面板模式下,当单击标头时,相应的片段将附加到一个新的PreferenceActivity中。这个包含PreferenceActivity的新片段从未调用onBuildHeaders()。它不需要显示它们。这是问题所在。
当将该片段旋转为双面板模式时,它没有任何标题列表可显示,因此它仅继续显示片段。即使它显示了标题列表,您也会遇到一些返回堆栈的问题,因为现在您将有两个显示标题的PreferenceActivity的副本。继续单击足够的标题,用户将得到一个相当冗长的活动堆栈,需要通过导航回来。因此,答案很简单。只需finish()该活动。然后它将加载具有标题列表并正确显示双面板模式的原始PreferenceActivity。 自动选择标题
需要处理的下一个问题是使用新修复程序从单面板模式切换到双面板模式时不会自动选择标题。您将得到一个标题列表,并且没有详细信息片段加载。这个修复不是那么简单。基本上,您只需要跟踪最后单击的标头,并确保在PreferenceActivity创建期间...总是选择一个标题。
在ICS中,这可能有点麻烦,因为API未公开内部跟踪的header列表的getter。Android已经保存了该列表,您可以通过使用相同的私有存储的内部字符串键检索它,但这只是一个糟糕的设计选择。相反,我建议您手动再次持久化它。
如果您不关心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);
    }
}
}

这是一个不错的解决方案,但我发现它仍然有一个问题。假设为了简单起见,当你处于横屏模式时,multi-pane 为 true。现在在纵向模式(single pane)下运行您的应用程序,选择一个标题,切换到横向模式(multi-pane),然后再切换到纵向模式。问题在于所选项目不再被恢复。它只是回到了主设置屏幕,显示所有标题。虽然不是什么大问题,但很奇怪... - android developer

0
下面代码的关键思想来自于与问题相关的Commonsware博客文章,因此它感觉很相关。我特别需要扩展这个概念以处理一个方向改变问题,这个问题听起来非常类似于问题中的那个,所以希望它能给你一个开始。
设置类不应该对方向问题有任何影响,但为了清晰起见,还是包括它。
根据我的代码注释,在onCreate中查看checkNeedsResource调用是否有所帮助:
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);
}

}


我不确定你在这里发布了什么,但这并不起作用。实际上,我不确定它如何处理任何方向的更改。我也不担心ICS之前的任何事情,因此那些已弃用的addPreferencesFromResource行为对我的问题不相关...你的首选项更改侦听器也是如此。 - Ifrit
它解决了我遇到的问题的原因是在onCreate中检查,这发生在方向更改后。它没有帮助你解决你遇到的问题,更多或少包含在我的“希望……”中。很高兴你坚持不懈,并发布了你的解决方案。 - Mark Larter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接