PreferenceFragment.java revision c56fc753e2e2d35221a1a4df353a435098268ec4
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.preference; 18 19import android.app.Activity; 20import android.app.Fragment; 21import android.content.Intent; 22import android.content.SharedPreferences; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.ListView; 30 31/** 32 * Shows a hierarchy of {@link Preference} objects as 33 * lists. These preferences will 34 * automatically save to {@link SharedPreferences} as the user interacts with 35 * them. To retrieve an instance of {@link SharedPreferences} that the 36 * preference hierarchy in this fragment will use, call 37 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} 38 * with a context in the same package as this fragment. 39 * <p> 40 * Furthermore, the preferences shown will follow the visual style of system 41 * preferences. It is easy to create a hierarchy of preferences (that can be 42 * shown on multiple screens) via XML. For these reasons, it is recommended to 43 * use this fragment (as a superclass) to deal with preferences in applications. 44 * <p> 45 * A {@link PreferenceScreen} object should be at the top of the preference 46 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy 47 * denote a screen break--that is the preferences contained within subsequent 48 * {@link PreferenceScreen} should be shown on another screen. The preference 49 * framework handles showing these other screens from the preference hierarchy. 50 * <p> 51 * The preference hierarchy can be formed in multiple ways: 52 * <li> From an XML file specifying the hierarchy 53 * <li> From different {@link Activity Activities} that each specify its own 54 * preferences in an XML file via {@link Activity} meta-data 55 * <li> From an object hierarchy rooted with {@link PreferenceScreen} 56 * <p> 57 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The 58 * root element should be a {@link PreferenceScreen}. Subsequent elements can point 59 * to actual {@link Preference} subclasses. As mentioned above, subsequent 60 * {@link PreferenceScreen} in the hierarchy will result in the screen break. 61 * <p> 62 * To specify an {@link Intent} to query {@link Activity Activities} that each 63 * have preferences, use {@link #addPreferencesFromIntent}. Each 64 * {@link Activity} can specify meta-data in the manifest (via the key 65 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML 66 * resource. These XML resources will be inflated into a single preference 67 * hierarchy and shown by this fragment. 68 * <p> 69 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use 70 * {@link #setPreferenceScreen(PreferenceScreen)}. 71 * <p> 72 * As a convenience, this fragment implements a click listener for any 73 * preference in the current hierarchy, see 74 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}. 75 * 76 * <a name="SampleCode"></a> 77 * <h3>Sample Code</h3> 78 * 79 * <p>The following sample code shows a simple preference fragment that is 80 * populated from a resource. The resource it loads is:</p> 81 * 82 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences} 83 * 84 * <p>The fragment implementation itself simply populates the preferences 85 * when created. Note that the preferences framework takes care of loading 86 * the current values out of the app preferences and writing them when changed:</p> 87 * 88 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java 89 * fragment} 90 * 91 * @see Preference 92 * @see PreferenceScreen 93 */ 94public abstract class PreferenceFragment extends Fragment implements 95 PreferenceManager.OnPreferenceTreeClickListener { 96 97 private static final String PREFERENCES_TAG = "android:preferences"; 98 99 private PreferenceManager mPreferenceManager; 100 private ListView mList; 101 private boolean mHavePrefs; 102 private boolean mInitDone; 103 104 /** 105 * The starting request code given out to preference framework. 106 */ 107 private static final int FIRST_REQUEST_CODE = 100; 108 109 private static final int MSG_BIND_PREFERENCES = 1; 110 private Handler mHandler = new Handler() { 111 @Override 112 public void handleMessage(Message msg) { 113 switch (msg.what) { 114 115 case MSG_BIND_PREFERENCES: 116 bindPreferences(); 117 break; 118 } 119 } 120 }; 121 122 final private Runnable mRequestFocus = new Runnable() { 123 public void run() { 124 mList.focusableViewAvailable(mList); 125 } 126 }; 127 128 /** 129 * Interface that PreferenceFragment's containing activity should 130 * implement to be able to process preference items that wish to 131 * switch to a new fragment. 132 */ 133 public interface OnPreferenceStartFragmentCallback { 134 /** 135 * Called when the user has clicked on a Preference that has 136 * a fragment class name associated with it. The implementation 137 * to should instantiate and switch to an instance of the given 138 * fragment. 139 */ 140 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); 141 } 142 143 @Override 144 public void onCreate(Bundle savedInstanceState) { 145 super.onCreate(savedInstanceState); 146 mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE); 147 mPreferenceManager.setFragment(this); 148 } 149 150 @Override 151 public View onCreateView(LayoutInflater inflater, ViewGroup container, 152 Bundle savedInstanceState) { 153 return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, 154 container, false); 155 } 156 157 @Override 158 public void onActivityCreated(Bundle savedInstanceState) { 159 super.onActivityCreated(savedInstanceState); 160 getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); 161 162 if (mHavePrefs) { 163 bindPreferences(); 164 } 165 166 mInitDone = true; 167 168 if (savedInstanceState != null) { 169 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 170 if (container != null) { 171 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 172 if (preferenceScreen != null) { 173 preferenceScreen.restoreHierarchyState(container); 174 } 175 } 176 } 177 } 178 179 @Override 180 public void onStart() { 181 super.onStart(); 182 mPreferenceManager.setOnPreferenceTreeClickListener(this); 183 } 184 185 @Override 186 public void onStop() { 187 super.onStop(); 188 mPreferenceManager.dispatchActivityStop(); 189 mPreferenceManager.setOnPreferenceTreeClickListener(null); 190 } 191 192 @Override 193 public void onDestroyView() { 194 mList = null; 195 mHandler.removeCallbacks(mRequestFocus); 196 mHandler.removeMessages(MSG_BIND_PREFERENCES); 197 super.onDestroyView(); 198 } 199 200 @Override 201 public void onDestroy() { 202 super.onDestroy(); 203 mPreferenceManager.dispatchActivityDestroy(); 204 } 205 206 @Override 207 public void onSaveInstanceState(Bundle outState) { 208 super.onSaveInstanceState(outState); 209 210 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 211 if (preferenceScreen != null) { 212 Bundle container = new Bundle(); 213 preferenceScreen.saveHierarchyState(container); 214 outState.putBundle(PREFERENCES_TAG, container); 215 } 216 } 217 218 @Override 219 public void onActivityResult(int requestCode, int resultCode, Intent data) { 220 super.onActivityResult(requestCode, resultCode, data); 221 222 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data); 223 } 224 225 /** 226 * Returns the {@link PreferenceManager} used by this fragment. 227 * @return The {@link PreferenceManager}. 228 */ 229 public PreferenceManager getPreferenceManager() { 230 return mPreferenceManager; 231 } 232 233 /** 234 * Sets the root of the preference hierarchy that this fragment is showing. 235 * 236 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 237 */ 238 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 239 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 240 mHavePrefs = true; 241 if (mInitDone) { 242 postBindPreferences(); 243 } 244 } 245 } 246 247 /** 248 * Gets the root of the preference hierarchy that this fragment is showing. 249 * 250 * @return The {@link PreferenceScreen} that is the root of the preference 251 * hierarchy. 252 */ 253 public PreferenceScreen getPreferenceScreen() { 254 return mPreferenceManager.getPreferenceScreen(); 255 } 256 257 /** 258 * Adds preferences from activities that match the given {@link Intent}. 259 * 260 * @param intent The {@link Intent} to query activities. 261 */ 262 public void addPreferencesFromIntent(Intent intent) { 263 requirePreferenceManager(); 264 265 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen())); 266 } 267 268 /** 269 * Inflates the given XML resource and adds the preference hierarchy to the current 270 * preference hierarchy. 271 * 272 * @param preferencesResId The XML resource ID to inflate. 273 */ 274 public void addPreferencesFromResource(int preferencesResId) { 275 requirePreferenceManager(); 276 277 setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(), 278 preferencesResId, getPreferenceScreen())); 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, 285 Preference preference) { 286 if (preference.getFragment() != null && 287 getActivity() instanceof OnPreferenceStartFragmentCallback) { 288 return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment( 289 this, preference); 290 } 291 return false; 292 } 293 294 /** 295 * Finds a {@link Preference} based on its key. 296 * 297 * @param key The key of the preference to retrieve. 298 * @return The {@link Preference} with the key, or null. 299 * @see PreferenceGroup#findPreference(CharSequence) 300 */ 301 public Preference findPreference(CharSequence key) { 302 if (mPreferenceManager == null) { 303 return null; 304 } 305 return mPreferenceManager.findPreference(key); 306 } 307 308 private void requirePreferenceManager() { 309 if (mPreferenceManager == null) { 310 throw new RuntimeException("This should be called after super.onCreate."); 311 } 312 } 313 314 private void postBindPreferences() { 315 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 316 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 317 } 318 319 private void bindPreferences() { 320 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 321 if (preferenceScreen != null) { 322 preferenceScreen.bind(getListView()); 323 } 324 } 325 326 /** @hide */ 327 public ListView getListView() { 328 ensureList(); 329 return mList; 330 } 331 332 private void ensureList() { 333 if (mList != null) { 334 return; 335 } 336 View root = getView(); 337 if (root == null) { 338 throw new IllegalStateException("Content view not yet created"); 339 } 340 View rawListView = root.findViewById(android.R.id.list); 341 if (!(rawListView instanceof ListView)) { 342 throw new RuntimeException( 343 "Content has view with id attribute 'android.R.id.list' " 344 + "that is not a ListView class"); 345 } 346 mList = (ListView)rawListView; 347 if (mList == null) { 348 throw new RuntimeException( 349 "Your content must have a ListView whose id attribute is " + 350 "'android.R.id.list'"); 351 } 352 mHandler.post(mRequestFocus); 353 } 354} 355