问题
My inner PreferenceScreen of PreferenceFragmentCompat is not showing, or seems to ignore tapping events.
I created MyPreferenceFragment
that extends PreferenceFragmentCompat
public class MyPreferenceFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences);
}
}
then I changed my theme at styles.xml
like
<style name="AppTheme" parent="@style/Theme.AppCompat.Light">
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
And finally create my preferences.xml
file like
<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>
At the build.gradle
I have added both:
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:preference-v7:23.0.1'
code of the Activity
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" />
Testing the above code I cannot open / get into the preference screen. Am I missing something? Why this isn't working?
回答1:
After spending many many hours with tries, searching and thankfully with some assistance from the creators of the support library. I've managed to make it work.
Step 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;
}
}
Tips.
- Do not add the fragment by
xml
you will have crashes on orientation changes. - Handle the recreations of activity / fragment add in
onCreate
so as to avoid losing your fragment when inside a preference screen. - The host activity of the fragment should implement the
PreferenceFragmentCompat.OnPreferenceStartScreenCallback
and recreate fragments of the same instance.
Step 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);
}
}
Tips.
- Use the method
setPreferencesFromResource
and take advantage of therootKey
of each screen. This way your code will be reused properly. - Keep in mind that if you have code like
findPreference
in your fragment it should havenull
checks as when you were in inner screens this will give you nothing.
The thing that is missing now is the implementation of the back arrow in the actionbar (home action) but this never works by itself ;-)
I' also created a demo app wrapping all this code you can find it on github.
回答2:
Solution is to start another fragment of the same class but with different root key. No Activity actions involved.
@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();
}
回答3:
I did it slightly differently, I'm launching a new activity for each screen. This seems to require less hacks: no need to mess with swapping fragments and background colors. You also get activity change animation as a bonus!
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>
回答4:
Another solution is to track the preference screens yourself and use the PreferenceFragmentCompat api
Here's the basic solution. (It doesn't cover all the edge cases, see advanced solution below)
Ensure you have configChanges="orientation" to prevent create/destroy
<activity
android:name=".MyPreferencesActivity"
android:configChanges="orientation" />
In the Activity you want to keep a Stack of PreferenceScreens and push/pop as needed
/* 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());
}
}
Optional: In your Fragment that extends PreferenceFragmentCompat, add setRetainInstance(true). (Note that without this it will likely work also, but it 'could' break occasionally. If you set 'Don't keep Activities' to true, and you'll see that it will get collected)
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setRetainInstance(true);
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences, rootKey);
...
That's it! Except that if you want to cover edge cases...
Advanced Solution (If you set 'Don't Keep Activities to True, you'll need to ensure you can rebuild everything from savedInstanceState)
Note that the accepted answer doesn't actually preserve state.
- set 'Don't Keep Activities' to True
- navigate to a nested PreferenceScreen
- Press home and then navigate back to the app
- It 'should' still be on the Nested PreferenceScreen, but it's actually on the root one
Full Advanced Solution using PreferenceFragmentCompat api and preserving the PreferenceScreen stack
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);
}
}
}
You can also handle all this in your Fragment instead of the Activity. Here's a gist of that https://gist.github.com/aaronvargas/0f210ad8643b512efda4acfd524e1232
回答5:
Based on @squirrel Intent solution, I made it work this way. It requires even less hacking.
Activity:
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();
}
}
Fragment:
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);
}
}
It is sad you need so much hacks in the support appcompat libraries for something that works flawlessly out-of-the-box in standard android.
来源:https://stackoverflow.com/questions/32487206/inner-preferencescreen-does-not-open-with-preferencefragmentcompat