内部PreferenceScreen无法使用PreferenceFragmentCompat打开

时间:2015-09-09 18:55:21

标签: android android-support-library android-appcompat preference-v7

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" />

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

8 个答案:

答案 0 :(得分:32)

在花了很多时间尝试,搜索并感谢支持库的创建者的一些帮助。我已成功地使它发挥作用。

第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上找到的所有代码。

答案 1 :(得分:6)

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

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

答案 2 :(得分:5)

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

public class MainActivity extends Activity {
public static List<ActivityManager.RunningAppProcessInfo> runningProcesses;
private static Logger logger = Logger.getLogger(MainActivity.class.getName());
private Map<Integer, String> pidMap = new TreeMap<Integer, String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Context context = getApplicationContext();
    ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    setContentView(R.layout.activity_main);
    List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses)
    {
        pidMap.put(runningAppProcessInfo.pid, runningAppProcessInfo.processName);
        logger.info("pid: " + runningAppProcessInfo.pid + " | " + runningAppProcessInfo.processName);
    }
}

的xml:

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

答案 3 :(得分:1)

另一种解决方案是自己跟踪首选项屏幕并使用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”,则需要确保可以从saveInstanceState重建所有内容)

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

  1. 将“请勿保留活动”设置为“真”
  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

的要点

答案 4 :(得分: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中完美无缺地运行。

答案 5 :(得分:0)

使用 Navigation Component 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)
        )
    }
}

enter image description here

enter image description here

答案 6 :(得分:0)

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

有了这个,您将不会松开后退纸叠并在按下后退按钮时返回到主页设置。

答案 7 :(得分:0)

这是来自 android documentation 的简单解决方案。要使用 PreferenceFragmentCompact 实现内部首选项屏幕导航,您所要做的就是将 fragment 属性添加到嵌入的首选项屏幕,给出要导航到的片段完整路径,例如。 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>