Working with Preferences and Saving State

来源:互联网 发布:安卓市场 知乎 编辑:程序博客网 时间:2024/06/05 09:31
 From chapter 13:  Working with Preferences and Saving State  340 


Android offers a robust and flexible framework for dealing with preferences.  And by 
Preferences, we mean those feature choices that a  user makes and saves to customize an 
application to their liking. For example, if the user wants a notifi cation via a ringtone or 
vibration or not at all, that is a preference the user saves;  the application remembers the 
choice until the user changes it. Android provides simple APIs that hide the reading and 
persisting of preferences. It also provides prebuilt user interfaces that you can use to let 
the user make preference selections. Because of the power built in to the Android 
preferences framework, we can also use preferences for more general-purpose storing of 
application state, to allow our application to  pick up where it left off for example, should 
our application go away and come back later. As another example, a game’s high scores 
could be stored as preferences, although you’ll want to use your own UI to display them. 
Before Android 3.0, prefer ences were managed a certain way, but then things changed. 
With the extra real estate of a tablet screen, preferences can be  visually arranged much 
more nicely than on a phone’s screen. Although the f oundational bits of preferences (the 
different types of preferences) stayed the same, the way they are displayed has changed 
quite a bit. This chapter covers the foundational aspects of preferences and shows how 
pre-3.0 preferences are  displayed. The chapter ends with coverage of 
PreferenceFragment and the new capabilities of  PreferenceActivity. 
Exploring the Preferences Framework 
Before we dig into Android’s preferences framework, let’s establish a scenario that 
would require the use of preferences and then explor e how we would go about 
addressing it. Suppose you are writing an app lication that provides a facility to search 
for airline flights. Moreover,  suppose that the app lication’s default se tting is to display 
flights based on the lowest cost, but the user can set a pr eference to always sort flights 
by the least number of stops or by a specific airline. How would you go about doing 
that?  

Understanding ListPreference 
Obviously, you would have to provide a UI for  the user to view the list of sort options. 
The list would contain radio buttons for each option, and the default (or current) 
selection would be pr eselected. To solve this proble m with the Android preferences 
framework requires very littl e work. First, you would create a preferences XML file to 
describe the preference, and t hen you use a prebuilt activity class that knows how to 
show and persist preferences. Li sting 13–1 shows the details. 
NOTE:  We will give you a URL at the end of the chap ter that you can use to download projects 
from this chapter. This will allow you to  import these projects into Eclipse directly. 
Listing 13–1. The Flight-Options Preferences XML F ile and Associated Activity Class 
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/flightoptions.xml --> <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"     android:key="flight_option_preference"     android:title="@string/prefTitle"     android:summary="@string/prefSummary">    <ListPreference     android:key="@string/selected_flight_sort_option"     android:title="@string/listTitle"     android:summary="@string/listSummary"     android:entries="@array/flight_sort_options"     android:entryValues="@array/flight_sort_options_values"     android:dialogTitle="@string/dialogTitle"     android:defaultValue="@string/flight_sort_option_default_value" />  </PreferenceScreen> 

public class FlightPreferenceActivity extends PreferenceActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         addPreferencesFromResource(R.xml.flightoptions);     } } 


Listing 13–1 contains an XML  fragment that represents t he flight-option preference 
setting. The listing also contains  an activity class that loads the preferences XML file. 
Let’s start with the XML. Android provides an end-to-end preferences framework. This 
means the framework lets you define your preferences, display the setting(s) to the user, 
and persist the user’s selection to the data store. You define y our preferences in XML 
under  /res/xml/ . To show preferences to the user,  you write an activity class that 
extends a predefined  Android class called android.preference.PreferenceActivity  and 

use the  addPreferencesFromResource() method to add the resource to the activity’s 
resource collection. The framework takes care of the rest (displaying and persisting). 
In this flight scenario, you create a file called  flightoptions.xml  at 
/res/xml/flightoptions.xml. You then create an activity class called 
FlightPreferenceActivity that extends the  android.preference.PreferenceActivity  
class. Next, you call addPreferencesFromResource(), passing in  R.xml.flightoptions . 
Note that the preference res ource XML points to several string resources. To ensure 
compilation, you need to add several string re sources to your project. We will show you 
how to do that shortly. For now, have a look at the UI generated  by Listing 13–1 (see 
Figure 13–1).  
Figure 13–1. The flight-options preference UI 
Figure 13–1 contains tw o views. The view on the left is called a  preference screen, and 
the UI on the right is a list preference . When the user selects F light Options, the Choose 
Flight Options view appears as a modal dialog with radio buttons for each option. The 
user selects an option, which immediately saves that opti on and closes the view. When 
the user returns to the options screen, the view reflects the saved  selection from before. 
The XML code in Listing 13–1 defines PreferenceScreen and then creates 
ListPreference as a child. For  PreferenceScreen, you set three properties:  key ,  title , 
and  summary .  key  is a string you can use to refer to  the item programmati cally (similar to 
how you use android:id);  title  is the screen’s title (Flight Options); and summary  is a 
description of the scr een’s purpose, shown below the title in a smaller font (Set Search 
Options, in this case). For the list preference, you set key ,  title , and  summary , as well as 
attributes for entries ,  entryValues ,  dialogTitle , and  defaultValue. Table 13–1 
summarizes these attributes. 
 
Table 13–1.  A Few Attributes of  android.preference.ListPreference  
Attribute  Description 
android:key   A  name or key for the option (such as 
selected_flight_sort_option ).
android:title  
The title of the option. 
android:summary   A  short summary of the option. 
android:entries  
The array of text items that the option can be set to. 
android:entryValues The key, or value, for each item. Note that each item has some text 
and a value. The text is defined by  entries , and the values are 
defined by entryValues . 
android:dialogTitle  
The title of the dialog—used if the view is shown as a modal dialog.
android:defaultValue 
The default value of the option from the list of items.  
Listing 13–2 contains the source of several other files for the example, which we’ll be 
talking about soon. 
Listing 13–2. Other Files from Our Example 
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/values/arrays.xml --> <resources> <string-array name="flight_sort_options">     <item>Total Cost</item>     <item># of Stops</item>     <item>Airline</item> </string-array> <string-array name="flight_sort_options_values">     <item>0</item>     <item>1</item>     <item>2</item> </string-array> </resources> <?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/values/strings.xml --> <resources>     <string name="app_name">Preferences Demo</string>     <string name="prefTitle">My Preferences</string>     <string name="prefSummary">Set Flight Option Preferences</string>     <string name="flight_sort_option_default_value">1</string>     <string name="dialogTitle">Choose Flight Options</string>     <string name="listSummary">Set Search Options</string>     <string name="listTitle">Flight Options</string>     <string name="selected_flight_sort_option">         selected_flight_sort_option</string> CHAPTER 13:  Working with Preferences and Saving State  343     <string name="menu_prefs_title">Settings</string> </resources> 

 // This file is MainActivity.java public class MainActivity extends Activity {     private TextView tv = null;     private Resources resources;      /** Called when the activity is first created. */     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.main);          resources = this.getResources();          tv = (TextView)findViewById(R.id.text1);                  setOptionText();     }          @Override     public boolean onCreateOptionsMenu(Menu menu)      {        MenuInflater inflater = getMenuInflater();        inflater.inflate(R.menu.mainmenu, menu);        return true;     }      @Override     public boolean onOptionsItemSelected (MenuItem item)     {        if (item.getItemId() == R.id.menu_prefs)        {            // Launch to our preferences screen.            Intent intent = new Intent()                    .setClass(this,                com.androidbook.preferences.sample.FlightPreferenceActivity.class);            this.startActivityForResult(intent, 0);        }        return true;     }      @Override     public void onActivityResult(int reqCode, int resCode, Intent data)     {         super.onActivityResult(reqCode, resCode, data);         setOptionText();     }          private void setOptionText()     {         SharedPreferences prefs =                  PreferenceManager.getDefaultSharedPreferences(this); //      This is the other way to get to the shared preferences: //      SharedPreferences prefs = getSharedPreferences( CHAPTER 13:  Working with Preferences and Saving State  344 //              "com.androidbook.preferences.sample_preferences", 0);         String option = prefs.getString(                 resources.getString(R.string.selected_flight_sort_option),                 resources.getString(R.string.flight_sort_option_default_value));         String[] optionText = resources.getStringArray(R.array.flight_sort_options);          tv.setText("option value is " + option + " (" +                  optionText[Integer.parseInt(option)] + ")");     } } 

When you run this application, you will first see a simple text message that says “option 
value is 1 (# of Stops).” Click the M enu button and then Settings to get to the 
PreferenceActivity. Click the back arrow when you’re  finished, and you will see any 
changes to the option text immediately. 
The first file to talk about is /res/values/arrays.xml. This file contains the two string 
arrays that we need to implem ent the option choices. The first array holds the text to be 
displayed, and the second holds the values that we’ll get back in our method calls plus 
the value that gets stored in  the preferences XML file. For our purposes, we chose to 
use array index values 0, 1, and 2 for  flight_sort_options_values. We could use any 
value that helps us run the application. If our option was numeric in nature (for example, 
a countdown timer starting value), then we  could have used values such as 60, 120, 
300, and so on. The values don’t need to be numeric at all as long as they make sense 
to the developer; the user doesn’t see t hese values unless you choose to expose them. 
The user only sees the text from the first string array  flight_sort_options . 
As we said earlier, the Android framework also takes care of persisting preferences. For 
example, when the user selects a sort option, An droid stores the sel ection in an XML file 
within the application’s /data  directory on the devi ce (see Figure 13–2). 
 
Figure 13–2. Path to an application’s saved preferences 
NOTE:  You will only be able to inspect shared preferences files in th e emulator. On a real device, 
the shared preferences files are not readable due to Android security. 
  
The actual file path is 
/data/data/[PACKAGE_NAME] /shared_prefs/ [PACKAGE_NAME] _preferences.xml . Listing 13–3 
shows the  com.androidbook.preferences.sample_preferences.xml file for our example. 
Listing 13–3. Saved Preferences for Our Example 
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map>     <string name="selected_flight_sort_option">1</string> </map> 


You can see that for a list preference, the preferences framework persists the selected 
item’s value using the list’s  key  attribute. Note also that the selected item’s value is 
stored—not the text. A word of caution here: becaus e the preferences XML  file is storing 
only the value and not the te xt, should you ever upgrade your application and change 
the text of the options or add items to  the string arrays, any value stored in the 
preferences XML file should still line up with the appropriate text after the upgrade. The 
preferences XML file is kept during the appli cation upgrade. If t he preferences XML file 
had  a “1” in it, and that meant “# of Stops” before the upgrade, it should still mean “# 
of Stops” after the upgrade. 
The next file we are interested in is  /res/values/strings.xml . We added several strings 
for our titles,  summaries, and menu items. There are two strings to pay particular 
attention to. The first is  flight_sort_option_default_value. We set the default value to 
1  to represent “# of Stops” in our example. It is usually a good idea to choose a default 
value for each option. If you don’t choose a  default value and no value has yet been 
chosen, the methods that return the value of the option will return  null. Your code 
would have to deal with null values in this case. The other interesting string is 
selected_flight_sort_option . Strictly speaking, the user is  not going to see this string, 
so we don’t need to put it inside  strings.xml  to provide alternate text for other 
languages. However, because this string value  is a key used in the method call to 
retrieve the value, by creating an ID out of it, we can ensure at compile time that we 
didn’t make a typographical error on the key’s name. 
Next up is the source code for our  MainActivity. This is a basic activity that gets a 
reference to the preferences and a handle to a TextView and then calls a method to read 
the current value of our option to set it into the  TextView. The layout for our application 
is not shown here but is simply a TextView to display a message about the current 
preference setting. Next, We set up our m enu and the menu callback. Within the menu 
callback, we launch an  Intent for the  FlightPreferenceActivity. Launching an intent 
for our preferences is the best way to get  to the preferences sc reen. You could use a 
menu or use a button to fire the intent. We’ll not repeat this code for later examples, but 
you would do the same thing with them, ex cept that you use the  appropriate activity 
class name. When  the preferences  Intent returns to us, we call the  setOptionText()  
method to update our TextView. 
 
There are two ways to get a  handle to the preferences: 
 The easiest is what we show in the example: that is, to call 
PreferenceManager.getDefaultSharedPreferences(this) . The  this 
argument is the context for finding  the default shared preferences, and 
the method will use the package name of  this to determine the file 
name and location of the preferences file, which happens to be the 
one created by our  PreferenceActivity, because they share the same 
package name.  
 The other way to get a handle to a preferences file is to use the 
getSharedPreferences() method call, passing in a file name argument 
as well as a mode argument. In Listing  13–2, we show this way, but it’s 
been commented out. Notice that you only specify the base part of the 
file name, not the path and not the file name extension. The mode 
argument controls permissions to our XML preferences file. In our 
preceding example, the  mode argument wouldn’t affect anything 
because the file is only created within the  PreferenceActivity, which 
sets the default permissions of MODE_PRIVATE (zero). We’ll discuss the 
mode argument later, in the sections on saving state. 
In most cases, you’ll use the first method  of locating the preferences. However, if you 
had multiple users for your application  on a device, and each user managed their own 
preferences, you’d need to use the second  option to keep the users’ preferences 
separate from each other. 
Inside of setOptionText() , with a reference to the pref erences, you call the appropriate 
methods to retrieve the preference values. In our example, we call getString() , because 
we know we’re retrieving a string value from the preferences. The first argument is the 
string value of the option key.  We noted before that using an ID ensures that we haven’t 
made any typographical errors while building our application. We could also have simply 
used the string "selected_flight_sort_option " for the first argument, but then it’s up to 
you to ensure that this string is exactly the same as other parts of your code where the 
key value is used. For the second argument, you specify a default value in case the 
value can’t be found in the preferences XML file. When your a pplication runs for the very 
first time, you don’t have a preferences XML f ile; so without specify ing a value for the 
second argument, you’ll always get null the first time. This is  true even though you’ve 
specified a default val ue for the option in the ListPreference specification in 
flightoptions.xml . In our example, we set a defaul t value in XML, and we used a 
resource ID to do it, so the code in  setOptionText()  can be used to read the value of 
the resource ID for the default value. Note that if we had not used an ID for the default 
value, it would be a lot tougher to read it directly from the ListPreference. By sharing a 
resource ID between the XML and our code, we have only one place in which to change 
the default value (that is, in  strings.xml ). 
In addition to displaying the  value of the preference, we also display the text of the 
preference. We’re taking a shortcut in our example, because we used array indices for 
the values in  flight_sort_options_values. By simply converting the value to an  int , we 
know which string to read from flight_sort_options . Had we used some other set of 
 
values for  flight_sort_options_values, we would need to determine the index of the 
element that is our preference  and then turn around and use that index to grab the text 
of our preference from flight_sort_options . 
Because we now have two activities in our application, we need two activity tags in 
AndroidManifest.xml . The first one is a standard activity of category  LAUNCHER. The 
second one is for a  PreferenceActivity, so we set the action name according to 
convention for intents, and we set the category to  PREFERENCE as shown in Listing 13–4. 
We probably don’t want the PreferenceActivity showing up on the Android page with 
all our other applications, which is why we chose not to use  LAUNCHER for it. You would 
need to make similar changes to AndroidManifest.xml  if you were to add other 
preferences screens. 
Listing 13–4.  PreferenceActivity entry in  AndroidManifest.xml  
       
 <activity android:name=".FlightPreferenceActivity"                   android:label="@string/prefTitle">             <intent-filter>                 <action android:name=  "com.androidbook.preferences.sample.intent.action.FlightPreferences" />                 <category                      android:name="android.intent.category.PREFERENCE" />             </intent-filter>         </activity> 


We showed one way to read a default value for a preference in code. Android provides 
another way that is a bit more elegant. In  onCreate(), we could have done the following 
instead: 
PreferenceManager.setDefaultValues(this, R.xml.flightoptions, false); 
Then, in  setOptionText() , we could have done this to read the option value: 
String option = prefs.getString( 
    resources.getString(R.string.selected_flight_sort_option), null); 
The first call will use flightoptions.xml  to find the default  values and generate the 
preferences XML file for us using the defaul t values. If we already have an instance of 
the  SharedPreferences  object in memory, it will upda te that too. The second call will 
then find a value for selected_flight_sort_option , because we took care of loading 
defaults first.  
After running this code the first time, if you look in the shared_prefs folder, you will see 
the preferences XML file even  if the preferences screen has  not yet been invoked. You 
will also see another file called _has_set_default_values.xml . This tells your application 
that the preferences XML file  has already been created with  the default values. The third 
argument to  setDefaultValues()—that is, false —indicates that you only want the 
defaults set in the preferences XML file  if it hasn’t been done before. If you choose  true 
instead, you’ll always reset the preferences  XML file with default values. Android 
remembers this information through the exist ence of this new XML file. If the user has 
selected new preference values, and you choose false  for the third argument, the user 
preferences won’t be overwritt en the next time this code runs. Notice that now we don’t 
 
need to provide a default value in the getString()  method call, because we should 
always get a value from the preferences XML file. 
If you need a reference to the preferences  from inside of an activity that extends 
PreferenceActivity, you could do it this way: 
SharedPreferences prefs = getPreferenceManager().getDefaultSharedPreferences(this); 
We showed you how to use the ListPreference view; now, let’s examine some other UI 
elements within the Android preferences framework. Namely, let’s talk about the 
CheckBoxPreference view and the  EditTextPreference view.  
Understanding CheckBoxPreference 
You saw that the  ListPreference preference displays a list as its UI element. Similarly, 
the  CheckBoxPreference preference displays a check-box  widget as its UI element. 
To extend the flight-search exam ple application, suppose you want to let the user set 
the list of columns to see in the result  set. This preference di splays the available 
columns and allows the user to choose the desired columns by marking the 
corresponding check boxes. The user interface for this example is shown in Figure 13–3, 
and the preferences XML file is shown in Listing 13–5. 
 
Figure 13–3. The user interface for the check-box preference 
Listing 13–5. Using  CheckBoxPreference 
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/chkbox.xml -->     <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="flight_columns_pref"                 android:title="Flight Search Preferences"                 android:summary="Set Columns for Search Results">         <CheckBoxPreference                 android:key="show_airline_column_pref"                 android:title="Airline"                 android:summary="Show Airline column" />          <CheckBoxPreference                 android:key="show_departure_column_pref"                 android:title="Departure"                 android:summary="Show Departure column" />         <CheckBoxPreference                 android:key="show_arrival_column_pref"                 android:title="Arrival"                 android:summary="Show Arrival column" />          <CheckBoxPreference                 android:key="show_total_travel_time_column_pref"                 android:title="Total Travel Time"                 android:summary="Show Total Travel Time column" />         <CheckBoxPreference                 android:key="show_price_column_pref"                 android:title="Price"                 android:summary="Show Price column" />  </PreferenceScreen> 

public class CheckBoxPreferenceActivity extends PreferenceActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         addPreferencesFromResource(R.xml.chkbox);     } } 


Listing 13–5 shows the  preferences XML file,  chkbox.xml, and a simple activity class that 
loads it using  addPreferencesFromResource(). As you can see, the UI has five check 
boxes, each of which is represented by a  CheckBoxPreference node in the preferences 
XML file. Each of the check boxes also has a key , which—as you would expect—is 
ultimately used to persist the state of the  UI element when it comes  time to save the 
selected preference. With  CheckBoxPreference, the state of the preference is saved 
when the user sets the state. In other  words, when the user checks or unchecks the 
preference control, its state is saved. Listi ng 13–6 shows the preference data store for 
this example. 
Listing 13–6. The Preferences Data Store for the Check Box Preference 
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map>     <boolean name="show_total_travel_time_column_pref" value="false" />     <boolean name="show_price_column_pref" value="true" />     <boolean name="show_arrival_column_pref" value="false" />     <boolean name="show_airline_column_pref" value="true" />     <boolean name="show_departure_column_pref" value="false" /> </map> 

Again, you can see that each preference is saved through its key  attribute. The data type 
of the  CheckBoxPreference is a boolean , which contains a  value of either  true or  false : 
true to indicate the pref erence is selected, and  false  to indicate other wise. To read the 
value of one of the check-box preferences, you would get access to the shared 
preferences and call the getBoolean() method, passing the  key  of the preference: 
 
boolean option = prefs.getBoolean("show_price_column_pref", false); 
One other useful feature of  CheckBoxPreference is that you can set different summary 
text depending on whether it’s  checked. The XML attributes are  summaryOn  and 
summaryOff. Now, let’s have a look at the  EditTextPreference. 
Understanding EditTextPreference 
The preferences framework also provides  a free-form text preference called 
EditTextPreference. This preference allows you to capture raw text rather than ask the 
user to make a selection. To demonstrate th is, let’s assume you hav e an application that 
generates Java code for the user. One of the preference settings of this application 
might be the default package name to use for  the generated classes. Here, you want to 
display a text field to the user for setting the package name for t he generated classes. 
Figure 13–4 shows the UI, and Li sting 13–7 shows the XML. 
 
Figure 13–4. Using the EditTextPreference 
Listing 13–7. An Example of an  EditTextPreference 
<?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/packagepref.xml --> <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="package_name_screen"                 android:title="Package Name"                 android:summary="Set package name">          <EditTextPreference                 android:key="package_name_preference"                 android:title="Set Package Name"                 android:summary="Set the package name for generated code"                 android:dialogTitle="Package Name" />  </PreferenceScreen> 

public class EditTextPreferenceActivity extends PreferenceActivity{ CHAPTER 13:  Working with Preferences and Saving State  351      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);              addPreferencesFromResource(R.xml.packagepref);     } } 

You can see that Li sting 13–6 defines  PreferenceScreen with a single 
EditTextPreference instance as a child. The generated UI for the listing features the 
PreferenceScreen on the left and the  EditTextPreference on the right (see Figure 13–4). 
When Set Package Name is selected, the user is presented with a dialog to input the 
package name. When the OK button is clicked, the preference is saved to the 
preference store. 
As with the other pref erences, you can obtain the  EditTextPreference from your activity 
class by using the preference’s key . Once you have the  EditTextPreference, you can 
manipulate the actual EditText by calling  getEditText() —if, for example, you want to 
apply validation, prep rocessing, or post-processing on t he value the user types in the 
text field. To get the text of the EditTextPreference, just use the getText()  method. 
Understanding RingtonePreference and 
MultiSelectListPreference 

There is another preference called  RingtonePreference, but we won’t cover that here. It 
follows the same rules as the others and  isn’t used much. And finally, a preference 
called  MultiSelectListPreference  was introduced in Android 3.0. The concept is 
somewhat similar to a ListPreference, but instead of only being able to select one item 
in the list, the user can select several or  none. Unfortunately, the implementation is 
buggy as of this writing. For  example, the values array d oes not seem to be used—only 
the entries array. This means  the XML preferences file contains the entry strings and not 
the corresponding values as the  ListPreference does. It is also a  mystery how to set the 
default values. For more information, see  http://code.google.com/p/android/ 
issues/detail?id=15966. 
Until this type of preference is fixed, you’re better off creating a set of 
CheckBoxPreferences. Figure 13–3 shows what a MultiSelectListPreference  looks like 
(more or less). 
Organizing Preferences 
The preferences framework provides some su pport for you to organize your preferences 
into categories. If you have a lot of prefer ences, for example, you can build a view that 
shows high-level categories of preferences. Users coul d then drill down into each 
category to view and manage preferences specific to that group.  
 
Using PreferenceCategory 
You can implement something like this in one of two ways. You can introduce nested 
PreferenceScreen elements within the root  PreferenceScreen, or you can use 
PreferenceCategory elements to get a similar result. Figure 13–5 and Listing 13–8 show 
how to implement the first technique, grouping preferences by using nested 
PreferenceScreen elements. 
The view on the left in Figure 13–5 displays options for two preference screens, one with 
the title Meats and the other with the title Vegetables. Clicking a group takes you to the 
preferences within that group. Listing 13–8 shows how to create nested screens. 
Figure 13–5. Creating groups of preferences by nesting  PreferenceScreen elements 
Listing 13–8.  Nesting PreferenceScreen Elements to Organize Preferences 
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="using_categories_in_root_screen"                 android:title="Categories"                 android:summary="Using Preference Categories">     <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="meats_screen"                 android:title="Meats"                 android:summary="Preferences related to meats">         <CheckBoxPreference                 android:key="fish_selection_pref"                 android:title="Fish"                 android:summary="Fish is healthy" /> CHAPTER 13:  Working with Preferences and Saving State  353         <CheckBoxPreference                 android:key="chicken_selection_pref"                 android:title="Chicken"                 android:summary="A common type of poultry" />         <CheckBoxPreference                 android:key="lamb_selection_pref"                 android:title="Lamb"                 android:summary="A young sheep" />      </PreferenceScreen>     <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="vegi_screen"                 android:title="Vegetables"                 android:summary="Preferences related to vegetables">         <CheckBoxPreference                 android:key="tomato_selection_pref"                 android:title="Tomato "                 android:summary="It's actually a fruit" />         <CheckBoxPreference                 android:key="potato_selection_pref"                 android:title="Potato"                 android:summary="My favorite vegetable" />      </PreferenceScreen>  </PreferenceScreen> 

You create the groups in Figure 13–5 by nesting  PreferenceScreen elements within the 
root PreferenceScreen. Organizing preferences this way is useful if you have a lot of 
preferences and you’re concerned about having the users scroll to find the preference 
they are looking for. If you don’t have a lot  of preferences but still want to provide high-level categories for your preferences, you can use  PreferenceCategory, which is the 
second technique we menti oned. Figure 13–6 and Listing 13–9 show the details. 
  
Figure 13–6. Using  PreferenceCategory to organize preferences 
Figure 13–6 shows the same groups we used in our previous example, but now organized 
with preference categories. The only difference between the XML in Listing 13–9 and the 
XML in Listing 13–8 is that you create a  PreferenceCategory for the nested screens 
rather than nest  PreferenceScreen elements. 
Listing 13–9. Creating Categories of Preferences 
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="using_categories_in_root_screen"                 android:title="Categories"                 android:summary="Using Preference Categories">      <PreferenceCategory         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="meats_category"                 android:title="Meats"                 android:summary="Preferences related to meats">          <CheckBoxPreference                 android:key="fish_selection_pref"                 android:title="Fish"                 android:summary="Fish is healthy" />         <CheckBoxPreference                 android:key="chicken_selection_pref"                 android:title="Chicken"                 android:summary="A common type of poultry" />         <CheckBoxPreference                 android:key="lamb_selection_pref" CHAPTER 13:  Working with Preferences and Saving State  355                 android:title="Lamb"                 android:summary="A young sheep" />      </PreferenceCategory>     <PreferenceCategory         xmlns:android="http://schemas.android.com/apk/res/android"                 android:key="vegi_category"                 android:title="Vegetables"                 android:summary="Preferences related to vegetables">         <CheckBoxPreference                 android:key="tomato_selection_pref"                 android:title="Tomato "                 android:summary="It's actually a fruit" />         <CheckBoxPreference                 android:key="potato_selection_pref"                 android:title="Potato"                 android:summary="My favorite vegetable" />      </PreferenceCategory>  </PreferenceScreen> 

Creating Child Preferences with Dependency 
Another way to organize preferences is to  use a preference dependency. This creates a 
parent-child relationship between preferences. For example,  we might have a preference 
that turns on alerts; and if alerts are on, there might be severa l other alert-related 
preferences to choose from. If the main alerts preference is off,  the other preferences 
are not relevant and should be disabled. Listing 13–10 shows the XML, and Figure 13–7 
shows what it looks like. 
Listing 13–10.  Preference Dependency in XML 
<PreferenceScreen>      <PreferenceCategory             android:title="Alerts">                      <CheckBoxPreference                 android:key="alert_email"                 android:title="Send email?" />                      <EditTextPreference                 android:key="alert_email_address"                 android:layout="?android:attr/preferenceLayoutChild"                 android:title="Email Address"                 android:dependency="alert_email" />                  </PreferenceCategory>                  </PreferenceScreen> 

Figure 13–7. Preference dependency 
Preferences with Headers 
With the introduction of Android 3.0, we got another way to organize preferences. You 
see this on tablets under the main Settings app. Because tablet screen real estate offers 
much more room than a smartphone does, it makes sense to display more preference 
information at the same time. To accomplish this, we use preference headers. Take a 
look at Figure 13–8 to see what we mean. 
 
Figure 13–8. Main Settings page with preference headers 
Notice that headers appear down the left side, like a vertical tab bar. As you click each item 
on the left, the screen to the right displays the preferences for that item. In Figure 13–8, 
Sound is chosen, and the sound preferences are displayed at right. The right side is a 
PreferenceScreen object, and this setup uses fr agments. Obviously, we need to do 
something different than what has been discussed so far in this chapter. 
The big change from Android 3.0 was the addition of headers to  PreferenceActivity. 
This also means using a new callback within PreferenceActivity to do the headers 
 setup. Now, when you extend  PreferenceActivity, you'll want to implement this 
method: 
   
 public void onBuildHeaders(List<Header> target) {         loadHeadersFromResource(R.xml.preferences, target);     } 


The  preferences.xml  file contains some new tags that look like this: 
<preference-headers         xmlns:android="http://schemas.android.com/apk/res/android">     <header android:fragment="com.example.PrefActivity$Prefs1Fragment"             android:icon="@drawable/ic_settings_sound"             android:title="Sound"             android:summary="Your sound preferences" />     ... 

Each header tag points to a class that extends PreferenceFragment. In the example just 
given, the XML specifies an i con, the title, and summary text (which acts like a subtitle). 
Prefs1Fragment is an inner class of  PreferenceActivity that could look something like 
this: 
    
public static class Prefs1Fragment extends PreferenceFragment {         @Override         public void onCreate(Bundle savedInstanceState) {             super.onCreate(savedInstanceState);             addPreferencesFromResource(R.xml.sound_preferences);         }     } 

All this inner class needs to do is pull in the appropriate preferences XML file, as shown. 
That preferences XML file contains the types of preference  specifications we covered 
earlier, such as ListPreference,  CheckBoxPreference,  PreferenceCategory, and so on. 
What’s very nice is that Android takes care of doing the right thing when the screen 
configuration changes and when the preferences are displa yed on a small screen. 
Headers behave like old pref erences when the screen is t oo small to display both 
headers and the preference screen to the right. That is, you only see the headers; and 
when you click a header, you then see only the appropriate preference screen. 
Manipulating Preferences Programmatically 
It goes without saying that  you might need to access the actual preference controls 
programmatically. For example, what if you need to provide the  entries  and 
entryValues  for the  ListPreference at runtime? You can de fine and access preference 
controls similarly to the way you define and access controls in layout files and activities. 
For example, to access the list preference defined in Listing 13–1, you would call the 
findPreference() method of PreferenceActivity, passing the preference’s key  (note 
the similarity to findViewById()). You would next cast the control to  ListPreference and 
then go about manipulating the control. For example, if you want to set the entries of the 
ListPreference view, call the  setEntries() method, and so on. Listing 13–11 shows 
what this might look like with a simple example of using code to  set up the preference. 
 Of course, you could also create the entire PreferenceScreen starting with 
PreferenceManager.createPreferenceScreen(). 
Listing 13–11.  Setting  ListPreference Values Programmatically 
public class FlightPreferenceActivity extends PreferenceActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         addPreferencesFromResource(R.xml.flightoptions);          ListPreference listpref = (ListPreference) findPreference(                                    "selected_flight_sort_option");          listpref.setEntryValues(new String[] {"0","1","2"});         listpref.setEntries(new String[] {"Food", "Lounge", "Frequent Flier Program"});     } } 

Saving State with Preferences 
Preferences are great for allowing users to customize applications to their liking, but we 
can use the Android preference framework for more than that. When your application 
needs to keep track of some data between invocations of the application, preferences 
are one way to accomplish the task. We’ve already talked about content providers for 
maintaining data. We could use custom files on the SD card. We can also use shared 
preference files and code. 
The  Activity class has a  getPreferences(int mode) method. This, in reality, simply 
calls getSharedPreferences() with the class name of the  activity as the tag plus the 
mode as passed in. The result is an activity- specific shared prefer ences file that you can 
use to store data about this activity across invocations. A simple example of how you 
could use this is shown in Listing 13–12. 
Listing 13–12.  Using Preferences to Save State for an Activity 
final String INITIALIZED = "initialized"; SharedPreferences myPrefs = getPreferences(MODE_PRIVATE);  boolean hasPreferences = myPrefs.getBoolean(INITIALIZED, false); if(hasPreferences) {         Log.v("Preferences", "We've been called before");         // Read other values as desired from preferences file...         someString = myPrefs.getString("someString", ""); } else {         Log.v("Preferences", "First time ever being called");         // Set up initial values for what will end up         // in the preferences file         someString = "some default value"; }  // Later when ready to write out values Editor editor = myPrefs.edit(); editor.putBoolean(INITIALIZED, true); editor.putString("someString", someString); // Write other values as desired editor.commit(); 

What this code does is acquire a reference to preferences for our activity class and 
check for the existence of a boolean “preference” called  initialized . We write 
“preference” in double quotation marks because this value is not something the user is 
going to see or set; it’s merely a value that we want to store in a shared preferences file 
for use next time. If we get a value, the shar ed preferences file exists, so our application 
must have been called before. We could then read other values out of the shared 
preferences file. For example, someString could be an activity variable that should be 
set from the last time this activity ran or set to  the default value if this is the first time. 
To write values to the shar ed preferences file, we mu st first get a preferences Editor. 
We can then put values into preferences and commit those changes when we’re 
finished. Note that, behind the scenes, Android is managing a  SharedPreferences  object 
that is truly shared. Ideally, there is never more than one  Editor active at a time. But it is 
very important to call the  commit() method so that the SharedPreferences  object and 
the shared preferences XML file get updated.  In the example, we wr ite out the value of 
someString to be used the next time this activity runs. 
You can access, write, and commit values anytime to your  preferences file. Possible 
uses for this include writing out high  scores for a game or recording when the 
application was last r un. You can also use the getSharedPreferences() call with 
different names to manage separate sets of preferences, all wi thin the same application 
or even the same activity. 
We’ve used MODE_PRIVATE for mode in our examples thus far. The other possible values 
of mode are  MODE_WORLD_READABLE  and MODE_WORLD_WRITEABLE. These modes are used 
when creating the shared preferences XML file to set the file permissions accordingly. 
Because the shared preferences files are stored within your  application’s data directory 
and therefore are not accessible to other applications, you only need to use 
MODE_PRIVATE. 
Using DialogPreference 
So far, you’ve seen how to use the out-of-the-box capabilities of the preferences 
framework, but what if you want to cr eate a custom preference? What if you want 
something like the slider of the Bright ness preference under Sc reen Settings? This is 
where DialogPreference comes in.  DialogPreference is the parent class of 
EditTextPreference and ListPreference. The behavior is a dialog that pops up, displays 
choices to the user, and is closed with a button or via the Back button. But you can 
extend DialogPreference to set up your own custom preference. Within your extended 
class, you provide your own layout, y our own click handlers, and custom code in 
onDialogClosed() to write the data for your pref erence to the shared preferences file. 
原创粉丝点击