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