自動生成された SettingsActivity を操作すると、すぐに古くなってしまいました。過去の定型コードを上下にスクロールする必要があります-さらに、黄色の警告でいっぱいで、私は黄色が嫌いです(ただし、非推奨の警告を完全に回避することはできません-What to use instead of "addPreferencesFromResource" in a PreferenceActivity? を参照してください)。クロス API の作成方法についてPreferenceActivity
も触れます - およびPreferenceFragment は互換性パッケージから意図的に除外されましたか?については、ディスカッションを参照してください)。onPostCreate()
また、 NPEを簡単に取得することもonPostStart()
できfindPreference()
ます。null
onStart()
現在、リフレクションを含むソリューションがありますが、リフレクションは回避する必要があります(地獄のように)-2つ前のバージョンのAndroidには興味がないため、リフレクションを回避できます( SDK_INTを十分にチェックしているか、新しいAndroidを使用するには遅延読み込みが必要ですを参照してくださいAPI ? なぜ? )。また、実行時にクラスを選択することを含む解決策があります-しかし、2つのクラスを持つことは最悪であり、とにかくOOPではありません(それらおよび他の解決策については、関連する質問への回答を参照してください: PreferenceActivity Android 4.0 and older )。
だから私は抽象基本クラスを思いつきました。これはJavaとオブジェクト指向の正しい方法です(Eclairが必要な場合を除き、回避するためにクラスのリフレクションや遅延ロードが必要な場合を除きますVerifyErrors
)、自動生成された定型コード:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import java.util.List;
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*
* Defines two abstract methods that need be implemented by implementators.
*/
public abstract class BaseSettings extends PreferenceActivity {
/**
* Determines whether to always show the simplified settings UI, where
* settings are presented in a single list. When false, settings are shown
* as a master/detail two-pane view on tablets. When true, a single pane is
* shown on tablets.
*/
private static final boolean ALWAYS_SIMPLE_PREFS = false;
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/** {@inheritDoc} */
@Override
public final boolean onIsMultiPane() { // never used by us
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Determines whether the simplified settings UI should be shown. This is
* true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
* doesn't have newer APIs like {@link PreferenceFragment}, or the device
* doesn't have an extra-large screen. In these cases, a single-pane
* "simplified" settings UI should be shown.
*/
private static final boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
@Override
protected final void onCreate(Bundle savedInstanceState) {
// disallow onCreate(), see comment in onPostCreate()
super.onCreate(savedInstanceState);
}
@Override
protected final void onStart() {
// disallow onStart(), see comment in onPostCreate()
super.onStart();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
// onPostCreate() probably is needed because onBuildHeaders() is called
// after onCreate() ? This piece of err code should be called
// onPostStart() btw - so yeah
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
// findPreference will return null if setupSimplePreferencesScreen
// hasn't run, so I disallow onCreate() and onStart()
}
/**
* Shows the simplified settings UI if the device configuration if the
* device configuration dictates that a simplified, single-pane UI should be
* shown.
*/
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
buildSimplePreferences();
}
/** {@inheritDoc} */
/*
* Subclasses of PreferenceActivity should implement onBuildHeaders(List) to
* populate the header list with the desired items. Doing this implicitly
* switches the class into its new "headers + fragments" mode rather than
* the old style of just showing a single preferences list (from
* http://developer
* .android.com/reference/android/preference/PreferenceActivity.html) -> IE
* this is called automatically - reads the R.xml.pref_headers and creates
* the 2 panes view - it was driving me mad - @inheritDoc my - It does not
* crash in Froyo cause isSimplePreferences is always true for
* Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB - @Override has
* nothing to do with runtime and of course on Froyo this is never called by
* the system
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public final void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(getHeadersXmlID(), target);
}
}
// =========================================================================
// Abstract API
// =========================================================================
/**
* Must return an id for the headers xml file. There you define the headers
* and the corresponding PreferenceFragment for each header which you must
* of course implement. This is used in the super implementation of
* {@link #onBuildHeaders(List)}
*
* @return an id from the R file for the xml containing the headers
*/
abstract int getHeadersXmlID();
/**
* Builds a pre Honeycomb preference screen. An implementation would use the
* (deprecated)
*{@link android.preference.PreferenceActivity#addPreferencesFromResource(int)}
*/
abstract void buildSimplePreferences();
}
サンプル実装:
public final class SettingsActivity extends BaseSettings implements
OnSharedPreferenceChangeListener {
private static final int PREF_HEADERS_XML = R.xml.pref_headers;
private static CharSequence master_enable;
private OnPreferenceChangeListener listener;
private static Preference master_pref;
private static final String TAG = SettingsActivity.class.getSimpleName();
private SharedPreferences sp;
/** Used as canvas for the simple preferences screen */
private static final int EMPTY_PREF_RESOURCE = R.xml.pref_empty;
private static int PREF_RESOURCE_SETTINGS = R.xml.pref_data_sync;
// abstract overrides
@Override
int getHeadersXmlID() {
return PREF_HEADERS_XML;
}
@Override
void buildSimplePreferences() {
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// THIS is a blank preferences layout - which I need so
// getPreferenceScreen() does not return null - so I can add a header -
// alternatively you can very well comment everything out apart from
// addPreferencesFromResource(R.xml.pref_data_sync);
addPreferencesFromResource(EMPTY_PREF_RESOURCE);
// Add 'data and sync' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_data_sync);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
}
// here is the work done
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
master_enable = getResources().getText(
R.string.enable_monitoring_master_pref_key);
listener = new ToggleMonitoringListener();
// DefaultSharedPreferences - register listener lest Monitor aborts
sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.registerOnSharedPreferenceChangeListener(this);
master_pref = findPreference(master_enable.toString());
}
@Override
protected void onResume() {
super.onResume();
master_pref.setOnPreferenceChangeListener(listener); // no way to
// unregister, see: https://stackoverflow.com/a/20493608/281545 This
// listener reacts to *manual* updates - so no need to be active
// outside onResume()/onPause()
}
@Override
protected void onDestroy() {
// may not be called (as onDestroy() is killable), but no leak,
// see: https://stackoverflow.com/a/20493608/281545
sp.unregisterOnSharedPreferenceChangeListener(this);
super.onDestroy();
}
/**
* Toggles monitoring and sets the preference summary.Triggered on *manual*
* update of the *single* preference it is registered with, but before this
* preference is updated and saved.
*/
private static class ToggleMonitoringListener implements
OnPreferenceChangeListener {
ToggleMonitoringListener() {}
@Override
public boolean
onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
final boolean enable = (Boolean) newValue;
Monitor.enableMonitoring(preference.getContext(), enable);
final CheckBoxPreference p = (CheckBoxPreference) preference;
preference.setSummary((enable) ? p.getSummaryOn() : p
.getSummaryOff());
return true;
}
return false;
}
}
/**
* This fragment is used when the activity is showing a two-pane
* settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public final static class DataSyncPreferenceFragment extends
PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "onCreate");
addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
master_pref = findPreference(master_enable.toString());
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (master_enable == null || master_pref == null) return;
if (master_enable.toString().equals(key)) {
refreshMasterPreference();
}
}
/**
* @param key
*/
private void refreshMasterPreference() {
final Boolean isMonitoringEnabled = AccessPreferences.get(this,
master_enable.toString(), false);
Log.w(TAG, "Stored value: " + isMonitoringEnabled);
final CheckBoxPreference p = (CheckBoxPreference) master_pref;
final boolean needsRefresh = p.isChecked() != isMonitoringEnabled;
if (needsRefresh) {
p.setChecked(isMonitoringEnabled);
p.setSummary((isMonitoringEnabled) ? p.getSummaryOn() : p
.getSummaryOff());
}
}
}
したがって、主なアイデアは、ヘッダーを使用して設定用の xml を提供することです。
public final void onBuildHeaders(List<Header> target) {
if (!isSimplePreferences(this)) {
loadHeadersFromResource(getHeadersXmlID(), target);
}
}
どこ:
@Override
int getHeadersXmlID() {
return PREF_HEADERS_XML;
}
とPREF_HEADERS_XML
:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- These settings headers are only used on tablets. -->
<header
android:fragment=".activities.SettingsActivity$DataSyncPreferenceFragment"
android:title="@string/pref_header_data_sync" />
</preference-headers>
で簡単な設定を設定しますbuildSimplePreferences()
これをより一般的な API にすることに興味があります (おそらく を含むsBindPreferenceSummaryToValueListener
)。アイデアは歓迎します。
ああ、はい、sBindPreferenceSummaryToValueListener の綿毛:
// FLUFF AHEAD:
// the fluff that follows is for binding preference summary to value -
// essentially wrappers around OnPreferenceChangeListener - just so
// you get an idea of the mess this autogenerated piece of, code, was
// formatter:off
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
/* 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) {
// For ringtone preferences, look up the correct display
// value using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
// preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone
// display name.
String name = ringtone
.getTitle(preference.getContext());
preference.setSummary(name);
}
}
} else if (preference instanceof CheckBoxPreference) {
boolean b = (Boolean) value;
Log.w(TAG, "::::value " + b);
final CheckBoxPreference p =(CheckBoxPreference)preference;
preference.setSummary((b) ? p.getSummaryOn() : p
.getSummaryOff());
Log.w(TAG, p.getKey() + " :: " + p.isChecked());
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
}; */
/**
* 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.
sBindPreferenceSummaryToValueListener.onPreferenceChange(
preference,
PreferenceManager.getDefaultSharedPreferences(
preference.getContext()).getString(preference.getKey(), ""));
} */