25

我的 PreferenceFragmentCompat 内部 PreferenceScreen 没有显示,或者似乎忽略了点击事件。

我创造了MyPreferenceFragment那个extends PreferenceFragmentCompat

public class MyPreferenceFragment extends PreferenceFragmentCompat {
 @Override
  public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    addPreferencesFromResource(R.xml.preferences);
  }
}

然后我改变了我的styles.xml主题

<style name="AppTheme" parent="@style/Theme.AppCompat.Light">
  <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>

最后创建我的preferences.xml文件

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference android:title="Check Me"/>
    <PreferenceScreen android:title="My Screen"> <!-- This is not opening -->
        <EditTextPreference android:title="Edit text" />
    </PreferenceScreen>
</PreferenceScreen>

build.gradle我添加了两个:

compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:preference-v7:23.0.1'

活动代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

activity_main.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment"
    android:name="com.mando.preferenceapp.MyPreferenceFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

测试上面的代码我无法打开/进入首选项屏幕。我错过了什么吗?为什么这不起作用?

4

8 回答 8

39

在花了很多时间尝试、搜索并感谢支持库创建者的帮助之后。我设法让它工作。

步骤1。 Activity

public class MyActivity extends AppCompatActivity implements
        PreferenceFragmentCompat.OnPreferenceStartScreenCallback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            // Create the fragment only when the activity is created for the first time.
            // ie. not after orientation changes
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(MyPreferenceFragment.FRAGMENT_TAG);
            if (fragment == null) {
                fragment = new MyPreferenceFragment();
            }

            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.replace(R.id.fragment_container, fragment, MyPreferenceFragment.FRAGMENT_TAG);
            ft.commit();
        }
    }

    @Override
    public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
                                           PreferenceScreen preferenceScreen) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        MyPreferenceFragment fragment = new MyPreferenceFragment();
        Bundle args = new Bundle();
        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
        fragment.setArguments(args);
        ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey());
        ft.addToBackStack(preferenceScreen.getKey());
        ft.commit();
        return true;
    }
}

提示。

  • 不要添加片段,xml否则会在方向更改时崩溃。
  • 处理活动/片段添加的娱乐,onCreate以避免在偏好屏幕内丢失片段。
  • 片段的宿主活动应该实现 PreferenceFragmentCompat.OnPreferenceStartScreenCallback并重新创建同一实例的片段。

第2步。 PreferenceFragment

public class MyPreferenceFragment extends PreferenceFragmentCompat {

    public static final String FRAGMENT_TAG = "my_preference_fragment";

    public MyPreferenceFragment() {
    }

    @Override
    public void onCreatePreferences(Bundle bundle, String rootKey) {
        setPreferencesFromResource(R.xml.preferences, rootKey);
    }

}

提示。

  • 使用该方法setPreferencesFromResource并利用rootKey每个屏幕的优势。这样您的代码将被正确重用。
  • 请记住,如果您findPreference的片段中有类似的代码,它应该进行null检查,因为当您在内部屏幕中时,这不会给您任何信息。

现在缺少的是在操作栏中执行后退箭头(主页操作),但这永远不会单独工作;-)

我还创建了一个包含所有这些代码的演示应用程序,您可以在github上找到它。

于 2015-09-12T15:15:46.793 回答
8

解决方案是启动同一类但具有不同根密钥的另一个片段。不涉及活动操作。

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
    if(getArguments() != null){
        String key = getArguments().getString("rootKey");
        setPreferencesFromResource(R.xml.preferences, key);
    }else{
        setPreferencesFromResource(R.xml.preferences, rootKey);
    }
}

@Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen){
    ApplicationPreferencesFragment applicationPreferencesFragment = new ApplicationPreferencesFragment();
    Bundle args = new Bundle();
    args.putString("rootKey", preferenceScreen.getKey());
    applicationPreferencesFragment.setArguments(args);
    getFragmentManager()
            .beginTransaction()
            .replace(getId(), applicationPreferencesFragment)
            .addToBackStack(null)
            .commit();
}
于 2017-09-12T14:45:11.197 回答
5

我的做法略有不同,我正在为每个屏幕启动一个新活动。这似乎需要更少的黑客攻击:无需弄乱交换片段和背景颜色。您还可以获得活动更改动画作为奖励!

public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
    final static private String KEY = "key";

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.preferences);

        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);

        if (savedInstanceState != null)
            return;

        Fragment p = new PreferencesFragment();

        String key = getIntent().getStringExtra(KEY);
        if (key != null) {
            Bundle args = new Bundle();
            args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, key);
            p.setArguments(args);
        }

        getSupportFragmentManager().beginTransaction()
                .add(R.id.preferences, p, null)
                .commit();
    }

    @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
        Intent intent = new Intent(PreferencesActivity.this, PreferencesActivity.class);
        intent.putExtra(KEY, preferenceScreen.getKey());
        startActivity(intent);
        return true;
    }

    @Override public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public static class PreferencesFragment extends PreferenceFragmentCompat implements ... {

        private static final String FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG";
        private String key;


        @Override public void onCreatePreferences(Bundle bundle, String key) {
            setPreferencesFromResource(R.xml.preferences, this.key = key);
        }

        // this only sets the title of the action bar
        @Override public void onActivityCreated(Bundle savedInstanceState) {
            ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
            if (actionBar != null) actionBar.setTitle((key == null) ? "Settings" : findPreference(key).getTitle());
            super.onActivityCreated(savedInstanceState);
        }
    }
}

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="0dp"
    android:orientation="vertical"
    android:padding="0dp"
    android:id="@+id/preferences">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />

    <!-- preference fragment will be inserted here programmatically -->

</LinearLayout>
于 2015-09-22T17:37:22.197 回答
2

另一种解决方案是自己跟踪首选项屏幕并使用 PreferenceFragmentCompat api

这是基本解决方案。(它不涵盖所有边缘情况,请参阅下面的高级解决方案)

确保您有 configChanges="orientation" 以防止创建/销毁

    <activity
        android:name=".MyPreferencesActivity"
        android:configChanges="orientation" />

在活动中,您希望保留一堆 PreferenceScreens 并根据需要推送/弹出

    /* track the screens as a Stack */
    private Stack<PreferenceScreen> preferenceScreens = new Stack<>();

    // ensure your Activity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback
    @Override
    public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
        preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
        preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
        return true;
    }

    @Override
    public void onBackPressed() {
        if (preferenceScreens.empty()) {
            super.onBackPressed();
        } else {
            prefsFragment.setPreferenceScreen(preferenceScreens.pop());
        }
    }

可选:在扩展 PreferenceFragmentCompat 的 Fragment 中,添加 setRetainInstance(true)。(请注意,如果没有这个,它也可能会起作用,但它“可能”偶尔会中断。如果您将“不保留活动”设置为 true,您会看到它会被收集)

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

        setRetainInstance(true);

        // Load the preferences from an XML resource
        setPreferencesFromResource(R.xml.preferences, rootKey);
    ...

而已!除了如果你想覆盖边缘情况......

高级解决方案(如果将“不保留活动”设置为 True,则需要确保可以从 savedInstanceState 重建所有内容)

请注意,接受的答案实际上并没有保留状态。

  1. 将“不保留活动”设置为 True
  2. 导航到嵌套的 PreferenceScreen
  3. 按主页,然后导航回应用程序
  4. 它“应该”仍然在 Nested PreferenceScreen 上,但它实际上在根目录上

使用 PreferenceFragmentCompat api 并保留 PreferenceScreen 堆栈的完整高级解决方案

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Stack;

/**
 * Class to Show the preference screen with Activity keeping state
 * @author Aaron Vargas
 */
public class MyPreferencesActivityStateful extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
    private static final String PREFERENCE_SCREENS = "PREFERENCE_SCREENS";
    private PrefsFragment prefsFragment;
    private Stack<PreferenceScreen> preferenceScreens = new Stack<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content. Re-Use if possible
        String tag = PrefsFragment.class.getName();
        prefsFragment = (PrefsFragment) getSupportFragmentManager().findFragmentByTag(tag);
        if (prefsFragment == null) prefsFragment = new PrefsFragment();

        getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
                prefsFragment, tag).commit();
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        // rebuild preferenceScreen stack
        for (String screenKey : Objects.requireNonNull(savedInstanceState.getStringArrayList(PREFERENCE_SCREENS))) {
            preferenceScreens.push((PreferenceScreen) prefsFragment.findPreference(screenKey));
        }

        PreferenceScreen preferenceScreen = preferenceScreens.pop();
        if (preferenceScreen != prefsFragment.getPreferenceScreen()) { // optimize if same
            prefsFragment.setPreferenceScreen(preferenceScreen);
        }
    }

    @Override
    public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
        preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
        preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
        return true;
    }

    @Override
    public void onBackPressed() {
        // account for onRestore not getting called equally to onSave
        while (preferenceScreens.contains(prefsFragment.getPreferenceScreen())) {
            preferenceScreens.remove(prefsFragment.getPreferenceScreen());
        }

        if (preferenceScreens.empty()) {
            super.onBackPressed();
        } else {
            prefsFragment.setPreferenceScreen(preferenceScreens.pop());
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        preferenceScreens.push(prefsFragment.getPreferenceScreen());

        ArrayList<String> keys = new ArrayList<>(preferenceScreens.size());
        for (PreferenceScreen screen : preferenceScreens) {
            keys.add(screen.getKey());
        }
        outState.putStringArrayList(PREFERENCE_SCREENS, keys);
    }

    public static class PrefsFragment extends PreferenceFragmentCompat {

        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

            setRetainInstance(true); // ensure in manifest - android:configChanges="orientation"

            // Load the preferences from an XML resource
            setPreferencesFromResource(R.xml.preferences, rootKey);
        }
    }

}

您也可以在 Fragment 而不是 Activity 中处理所有这些。这是一个要点https://gist.github.com/aaronvargas/0f210ad8643b512efda4acfd524e1232

于 2018-11-02T20:06:09.037 回答
2

使用导航组件Android Jetpack)和 Kotlin 现在非常容易:

class PrefsFragment : PreferenceFragmentCompat() {
    private val args: PrefsFragmentArgs by navArgs()

    override fun onCreatePreferences(state: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.prefs, args.rootKey)
    }

    override fun onNavigateToScreen(preferenceScreen: PreferenceScreen?) {
        findNavController().navigate(
            PrefsFragmentDirections.changeRoot(preferenceScreen!!.key)
        )
    }
}

在此处输入图像描述

在此处输入图像描述

于 2019-12-31T20:54:33.223 回答
0

这是来自android 文档的简单解决方案。要使用 PreferenceFragmentCompact 实现内部首选项屏幕导航,您只需将片段属性添加到嵌入式首选项屏幕,以提供片段完整路径以导航到例如。com.example.FragmentName

示例代码:

 <PreferenceCategory app:title="@string/choose_theme"
        android:icon="@drawable/ic_baseline_color_lens_24">
        <SwitchPreference
            android:title="@string/apply_night_mode"
            android:key="@string/key_enable_night_mode"/>
        <PreferenceScreen
            android:fragment="com.example.simbokeyboard.BlankFragment"
            android:title="Custom Theme"
            android:summary="@string/theme_summary">
            <Preference
                android:key="@string/choose_theme"
                android:title="@string/choose_theme"
                android:layout="@layout/theme_chooser"/>
        </PreferenceScreen>
    </PreferenceCategory>
于 2021-07-24T22:39:38.847 回答
0

替代使用导航组件 + androidx.appcomat: https ://stackoverflow.com/a/59732509/5437789

这样,当您按下后退按钮时,您不会松开后退堆栈并返回主页面设置。

于 2020-01-14T11:10:08.300 回答
0

基于@squirrel Intent 解决方案,我使它以这种方式工作。它需要更少的黑客攻击。
活动:

import android.support.v7.app.AppCompatActivity;

public class SettingsActivity extends AppCompatActivity {

    public static final String TARGET_SETTING_PAGE = "target";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        SettingsFragment settingsFragment = new SettingsFragment();
        Intent intent = getIntent();
        if (intent != null) {
            String rootKey = intent.getStringExtra(TARGET_SETTING_PAGE);
            if (rootKey != null) {
                settingsFragment.setArguments(Bundler.single(TARGET_SETTING_PAGE, rootKey));
            }
        }

        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, settingsFragment)
                .commit();
    }
}

分段:

import android.support.v14.preference.PreferenceFragment;

public class SettingsFragment extends PreferenceFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle arguments = getArguments();
        if (arguments != null && arguments.getString(TARGET_SETTING_PAGE) != null) {
            setPreferencesFromResource(R.xml.preferences, arguments.getString(TARGET_SETTING_PAGE));
        } else {
            addPreferencesFromResource(R.xml.preferences);
        }
    }

    @Override
    public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
        Intent intent = new Intent(getActivity(), SettingsActivity.class)
                .putExtra(TARGET_SETTING_PAGE, preferenceScreen.getKey());
        startActivity(intent);

        super.onNavigateToScreen(preferenceScreen);
    }
}

很遗憾,您需要在支持 appcompat 库中进行如此多的黑客攻击,才能在标准 android 中完美地开箱即用。

于 2017-05-07T09:19:35.497 回答