安卓PreferenceFragment的正确设置方法是什么?

9
我正在尝试在Android应用程序中实现基本的设置活动,并且要么获得一个空白的白屏,要么崩溃。我看到的文档和示例并没有帮助,因为它们要么过时,要么不一致。例如,根据您查找的位置,设置活动应该扩展 ActivityPreferenceActivity 或 AppCompatPreferenceActivity(File>New>Activity>Settings Activity的一部分)。
developer.android.com说您应该实现以下内容:
public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
            .replace(android.R.id.content, new SettingsFragment())
            .commit();
    }
}

然而,Android Studio 生成的设置活动并没有为其创建的三个片段中的任何一个调用此函数。它使用首选项头。

所以这里是我的问题:

  1. 如果您正在使用一个简单的、单一的 preferences.xml 文件,其中包含一个 PreferenceFragment,并且不需要预先API 19兼容性,那么 SettingsActivity 应该扩展哪个类?Activity、PreferenceActivity 还是 AppCompatPreferenceActivity(对于所有支持方法和委托)?
  2. 您是否需要在 SettingsActivity.onCreate() 中调用 getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit()
  3. 通过各种组合,我要么得到一个空白的白色设置屏幕,没有操作栏,要么崩溃。如何正确地设置一个单一的 PreferencesFragment,在一个显示应用程序操作栏的活动中?
4个回答

5
让我们假设我们想要一个设置屏幕,其中包括如下所示的一个复选框首选项片段: enter image description here 以下是一步一步构建设置活动的指南,您可以在其中添加一些首选项以切换或更改 Android 应用程序的配置:
  1. Add a dependency for support of preference fragment in build.gradle file for app module:

    dependencies {
        compile 'com.android.support:preference-v7:25.1.0'
    }
    
  2. Add xml Android resource directory inside res directory.

  3. Inside xml directory, add a new XML resource file named pref_visualizer.xml as below. We're going to add one check-box preference fragment inside it.

    <?xml version="1.0" encoding="utf-8"?>
    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
        <CheckBoxPreference
            android:defaultValue="true"
            android:key="show_base"
            android:summaryOff="Bass will not be shown currently."
            android:summaryOn="Bass will be shown currently."
            android:title="Show Bass"
            />
    </PreferenceScreen>
    

    PreferenceScreen is the root tag which can hold as many preference fragments as you want. If you want to add more configurations of type list or text box then you need to add it here as a child of PreferenceScreen tag.

  4. Add a new Java class named SettingsFragment which will host PreferenceScreen. It should extend PreferenceFragmentCompat class as shown below:

    import android.content.SharedPreferences;
    import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
    import android.os.Bundle;
    import android.support.v7.preference.CheckBoxPreference;
    import android.support.v7.preference.EditTextPreference;
    import android.support.v7.preference.ListPreference;
    import android.support.v7.preference.Preference;
    import android.support.v7.preference.PreferenceFragmentCompat;
    import android.support.v7.preference.PreferenceScreen;
    import android.widget.Toast;
    
    
    public class SettingsFragment extends PreferenceFragmentCompat {
    
    
    @Override
    public void onCreatePreferences(Bundle bundle, String s) {
            addPreferencesFromResource(R.xml.pref_visualizer);
        }
    }
    
  5. Now comes the final part where we build the association between an activity in the app and SettingsFragment class which hosts PreferenceScreen. Add a new activity named SettingsActivity which inherits from AppCompatActivity class. SettingsActivity class will act as the container for PreferenceScreen.

SettingsActivity的Java文件:

import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;

public class SettingsActivity extends AppCompatActivity {

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

}

以下是SettingsActivity的布局文件(activity_settings.xml)。其中,android.name属性至关重要。它将此活动与您整个项目中从PreferenceFragmentCompat类继承的任何类连接起来。我只有一个这样的类,名为SettingsFragment。如果您的应用程序具有多个设置屏幕,则可能会有多个从PreferenceFragmentCompat类继承的类。

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_settings"
    android:name="android.example.com.visualizerpreferences.SettingsFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

你已经准备就绪!

在 https://developer.android.com/reference/android/preference/PreferenceScreen 上查询,该类可通过 "android.preference.PreferenceScreen" 导入。为什么选择另一个名称“android.support.v7.preference.PreferenceScreen”? - Kolodez

4

SettingsActivity应该继承哪个类?

对我来说有效的方法是继承AppCompatActivity类。

static final String ANIMATION = "animation" ;
static final String COUNTDOWN_ON_OFF = "countdown_on_off";

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

    if (getFragmentManager().findFragmentById(android.R.id.content) == null)
    {
        getFragmentManager().beginTransaction().add(android.R.id.content, new Prefs()).commit();
    }
}

我删除了与偏好设置标题相关的所有生成代码,并保留了一些模板方法/变量(这些是Android Studio在早期版本中生成的),用于我的PreferenceFragment

public static class Prefs extends PreferenceFragment
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);

        // Bind the summaries of EditText/List/Dialog/Ringtone preferences
        // to their values. When their values change, their summaries are
        // updated to reflect the new value, per the Android Design
        // guidelines.

        // findPreference() uses android:key like in preferences.xml !

        bindPreferenceSummaryToValue(findPreference(ANIMATION));

    }
}

我在我的Activity类中使用了一个静态方法(从模板中调整而来)。您可能还想检查其他的首选项类型:

 /**
 * Binds a preference's summary to its value. More specifically, when the
 * preference's value is changed, its summary (line of text below the
 * preference title) is updated to reflect the value. The summary is also
 * immediately updated upon calling this method. The exact display format is
 * dependent on the type of preference.
 *
 * @see #sBindPreferenceSummaryToValueListener
 */
private static void bindPreferenceSummaryToValue(Preference preference)
{
    // Set the listener to watch for value changes.
    preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);

    // Trigger the listener immediately with the preference's
    // current value.

    if (preference instanceof CheckBoxPreference)
    {
        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                                                                 PreferenceManager
                                                                         .getDefaultSharedPreferences(preference.getContext())
                                                                        .getBoolean(preference.getKey(), true));
    }
    else
    {
        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                                                                 PreferenceManager
                                                                         .getDefaultSharedPreferences(preference.getContext())
                                                                         .getString(preference.getKey(), ""));
    }
}

最后,Activity 中的 Preference.OnPreferenceChangeListener 作为静态变量(也是从模板中适应而来):

   /**
 * A preference value change listener that updates the preference's summary
 * to reflect its new value.<br>
 * template by Android Studio minus Ringtone Preference
 */
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener()
{
    @Override
    public boolean onPreferenceChange(Preference preference, Object value)
    {
        String stringValue = value.toString();

        if (preference instanceof ListPreference)
        {
            // For list preferences, look up the correct display value in
            // the preference's 'entries' list.
            ListPreference listPreference = (ListPreference) preference;
            int index = listPreference.findIndexOfValue(stringValue);

            // Set the summary to reflect the new value.
            preference.setSummary(
                    index >= 0
                            ? listPreference.getEntries()[index]
                            : null);

        }
        else if (preference instanceof RingtonePreference)
        {
            // my app didn't need that
            return true;
        }
        else if (preference instanceof CheckBoxPreference)
        {
            Context ctx = preference.getContext();
            boolean isChecked = (Boolean) value;

            if (preference.getKey().equals(ANIMATION))
            {
                preference.setSummary(isChecked ? ctx.getString(R.string.sOn) : ctx.getString(R.string.sOff));
            }
            else if (preference.getKey().equals(COUNTDOWN_ON_OFF))
            {
                preference.setSummary(isChecked ? ctx.getString(R.string.sWhenPaused) : ctx.getString(R.string.sNever) );
            }
        }
        else
        {
            // For all other preferences, set the summary to the value's
            // simple string representation.
            preference.setSummary(stringValue);
        }
        return true;
    }
};
}

这个程序可以工作,但是操作栏(action bar)不见了。在SettingsActivity.onCreate()中,调用getSupportActionBar()返回null,即使该活动已设置为MainActivity的子活动,并且MainActivity在AndroidManifest.xml中有一个操作栏。我还在SettingsFragment.onCreate()中调用了setHasOptionsMenu(),但这可能没有什么用。你是如何显示ActionBar的? - Phil
1
@Phil - 我使用了一个默认带有ActionBar的Activity类型。如果你需要设置你的ActionBar,那么你应该从你的MainActivity中复制那一部分,并将Fragment放置在工具栏下面的容器中,而不是R.id.content中。 - Bö macht Blau
感谢 @0X0nosugar。我问这样一个基本的问题的原因是我正在使用Android Studio插入的SettingsActivity,它不需要膨胀工具栏,也不定义XML片段或指定其位置。它只是调用getSupportActionBar()并得到它。我的SettingsActivity基于它,调用getSupportActionBar()并得到null。要么我在某个地方遗漏了关键逻辑,要么标题样式首选项包括一些隐含的ActionBar处理。 - Phil
1
好的,我找到了阻止操作栏的原因。是我的错误。我把android:theme="@style/AppTheme.NoActionBar"放错了位置。当我将它放在正确的标签<activity>中,并与<application>中的android:theme=@style/AppTheme一起使用时,操作栏就出现了。再次感谢。 - Phil
1
@Phil - 不客气 :) 很抱歉我之前没有时间回复你,但我的猜测也是,是否有ActionBar取决于设置Activity的样式。祝编码愉快! - Bö macht Blau

2
这里是关于Kotlin和android-x的内容:
gradle:
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation "androidx.preference:preference-ktx:1.1.0-rc01"
implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'androidx.collection:collection-ktx:1.1.0'
implementation 'androidx.fragment:fragment-ktx:1.2.0-alpha01'

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState == null)
            supportFragmentManager.commit { replace(android.R.id.content, PrefsFragment()) }
    }

    class PrefsFragment : PreferenceFragmentCompat() {
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    }
}

preferences.xml

<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

  <androidx.preference.Preference android:title="hello"/>
</androidx.preference.PreferenceScreen>

0
除了RBT给出的答案,还必须指定一个首选项主题,否则该应用程序将崩溃并显示一个IllegalStateException。
在styles.xml文件中,只需在Activity的主题中添加以下行即可。
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>

需要一个Fragment吗?我只想要一个纯粹的Activity,没有Fragment。我看到Google自己的页面和示例都建议使用Fragment。 - Csaba Toth
这取决于您的需求和想要实现的目标。如果您只想存储一些数据并稍后恢复它,您可能需要使用SharedPreferences。 - Hasan El-Hefnawy
我以前做过自定义首选项。好处是PreferencesFragmentCompat可以用极少的管道代码完成工作,这也是我的目标。但是添加一个不必要的片段本身就是一种管道代码。因此,如果有PreferencesActivityCompat或类似的东西,我们几乎可以达到目标:没有管道代码。 - Csaba Toth

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