【问题标题】:Issues with dual-pane preference screens双窗格首选项屏幕的问题
【发布时间】:2013-08-10 21:51:22
【问题描述】:

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

设置
这仅适用于 ICS 及更高版本。我有一个PreferenceActivity,它加载了preference-headers。每个标头都与Fragment 链接,后者又会加载PreferenceScreen。千篇一律。

详情
一切都很顺利,直到我注意到 Android 只会自动切换到某些屏幕的两窗格外观。经过一些研究,我从Commonsware post 了解到,Android 只会对 sw720dp 这样做。如果你问我有点浪费,因为许多设备 def 有足够的空间容纳两个窗格。所以我重写了onIsMultiPane() 方法,为w600dp 及更高版本返回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);
}
}


首选项标头片段:

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

【问题讨论】:

  • 什么“说”调试视角?
  • 你有一个包含状态“可见”的值,我的意思是。
  • PreferenceActivity 处理可见性变化。我特别没有改变任何一个。上面的代码就是我所做的一切。
  • 您是否试图通过在清单中使用它来避免活动破坏和重新创建:android:configChanges="orientation|keyboardHidden"?如果是这样,那么这可能是原因。我怀疑这是因为当你改变方向时,Android 应该从头开始重建你的活动,但它似乎不在这里。我猜这只是需要检查的东西。
  • 不。我没有覆盖配置更改。考虑到缺乏答案,我只是选择正常使用偏好。

标签: java android android-fragments android-preferences preferenceactivity


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

  • 这是一个不错的解决方案,但我发现它仍然存在一个问题。假设为简单起见,如果您处于横向模式,则多窗格为真。现在以纵向模式(单窗格)运行您的应用程序,选择标题,切换到横向(多窗格),然后切换到纵向模式。问题是所选项目不再恢复。它刚刚回到主设置屏幕,显示所有标题。不过也不是什么大问题。真是奇怪……
【解决方案2】:

以下代码背后的关键思想来自问题中链接的 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 行为与我的问题无关......您的偏好也没有改变监听器。
  • 它解决了我遇到的问题的原因是签入 onCreate,它发生在方向更改之后。我的“希望……”中或多或少地涵盖了它对您遇到的问题没有帮助。很高兴你坚持下去,并发布了你的决议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-12
  • 1970-01-01
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多