Android ApiDemos示例解析(35):App->Preferences->Advanced preferences

前篇文章Android ApiDemo示例解析(31):App->Preferences->Launching preferences 中用到瞭Advanced preferences 中定義的AdvancedPreferences。

本篇具體介紹AdvancedPreferences, 這個例子稱為Advanced ,是因為它涉及到瞭自定義Preference, 並在一個工作線程中刷新某個Preference的值。

Preference 為顯示在PreferenceActivity (一般以列表顯示)在某個偏好的基類。有點類似於顯示在Activity中的某個View。Preference 實際存取的是對應在Shared Preferences中一項,而Preference定義的key也就是用來訪問Shared Preferences的key值。

和View一樣可以自定義View,在Android中也可以自定義Preference,用來顯示管理應用自定義的程序偏好。本例 MyPreference 自定義一個Preference 用來存儲用戶點擊該Preference的次數,類型為整數,初始值定義為100。它在advanced_preferences.xml 對應的定義如下:

<com.example.android.apis.app.MyPreference
android:key=”my_preference”
android:title=”@string/title_my_preference”
android:summary=”@string/summary_my_preference”
android:defaultValue=”100″ />

Preference 定義瞭很多屬性,比如Default Value, dependency, enabled, icon ,key 等等都有對應的方法來操作。並且提供瞭兩個Listener:PreferenceChangeListener, PreferenceClickListener ,允許應用程序響應Preference值變化事件,或是用戶點擊Preference事件。

這裡按照MyPreference 代碼順序說明一下如何自定義一個Preference。

1.  派生於 Preference基類。

[java]
public class MyPreference extends Preference 
public class MyPreference extends Preference

2. 和自定義View類似可以為自定義Preference 自定義Layout。 MyPreference 使用R.layout.preference_widget_mypreference  ,定義很簡單隻有一個TextView ,其id為mypreference_widget。 一般在構造函數中使用setWidgetLayoutResource為Preference派生類設置Layout資源。

[java] view plaincopyprint?
// This is the constructor called by the inflater  
public MyPreference(Context context, AttributeSet attrs) { 
 super(context, attrs); 
  
 setWidgetLayoutResource(R.layout.preference_widget_mypreference); 

// This is the constructor called by the inflater
public MyPreference(Context context, AttributeSet attrs) {
 super(context, attrs);
 
 setWidgetLayoutResource(R.layout.preference_widget_mypreference);
}

3.  如有需要為自定義的Layout中的View 設置屬性,可以在onBindView(View view)中完成。下面代碼為TextView設置值為mClickCounter。

[java] 
@Override 
 protected void onBindView(View view) { 
 super.onBindView(view); 
  
 // Set our custom views inside the layout  
 final TextView myTextView 
 = (TextView) view.findViewById(R.id.mypreference_widget); 
 if (myTextView != null) { 
 myTextView.setText(String.valueOf(mClickCounter)); 
 } 
 } 
@Override
 protected void onBindView(View view) {
 super.onBindView(view);
 
 // Set our custom views inside the layout
 final TextView myTextView
 = (TextView) view.findViewById(R.id.mypreference_widget);
 if (myTextView != null) {
 myTextView.setText(String.valueOf(mClickCounter));
 }
 }

 

4. 如果為該自定義Preference 在XML定義瞭初值,比如 MyPreference的初值android:defaultValue=”100″,我們想在代碼中使用這個初值來初始化變量mClickCounter 。mClickCounter 類型為整數,這個變量就是用來保存用戶的按鍵次數的。

[java] 
@Override 
protected Object onGetDefaultValue(TypedArray a, int index) { 
 // This preference type's value type is Integer, so we read the default  
 // value from the attributes as an Integer.  
 return a.getInteger(index, 0); 

  
@Override 
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 
 if (restoreValue) { 
 // Restore state  
 mClickCounter = getPersistedInt(mClickCounter); 
 } else { 
 // Set state  
 int value = (Integer) defaultValue; 
 mClickCounter = value; 
 persistInt(value); 
 } 

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
 // This preference type's value type is Integer, so we read the default
 // value from the attributes as an Integer.
 return a.getInteger(index, 0);
}
 
@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
 if (restoreValue) {
 // Restore state
 mClickCounter = getPersistedInt(mClickCounter);
 } else {
 // Set state
 int value = (Integer) defaultValue;
 mClickCounter = value;
 persistInt(value);
 }
}

5 重載 onSaveInstanceState,onRestoreInstanceState ,這兩個方法是用來臨時保存或是恢復一些變量值。在Preference 調用persistInt,persistBoolean, persistString等之前,preference 對應的值還沒有被保存在或是更新在Shared preferences 中,如果這時用戶旋轉屏幕,則造成Activity重新創建,我們需要在屏幕旋轉時用戶選擇項會保留,可以使用onSaveInstanceState,onRestoreInstanceState來保持一些臨時數據。

[java] 
@Override 
protected Parcelable onSaveInstanceState() { 
 /*
 * Suppose a client uses this preference type without persisting. We
 * must save the instance state so it is able to, for example, survive
 * orientation changes.
 */ 
  
 final Parcelable superState = super.onSaveInstanceState(); 
 if (isPersistent()) { 
 // No need to save instance state since it's persistent  
 return superState; 
 } 
  
 // Save the instance state  
 final SavedState myState = new SavedState(superState); 
 myState.clickCounter = mClickCounter; 
 return myState; 

  
@Override 
protected void onRestoreInstanceState(Parcelable state) { 
 if (!state.getClass().equals(SavedState.class)) { 
 // Didn't save state for us in onSaveInstanceState  
 super.onRestoreInstanceState(state); 
 return; 
 } 
  
 // Restore the instance state  
 SavedState myState = (SavedState) state; 
 super.onRestoreInstanceState(myState.getSuperState()); 
 mClickCounter = myState.clickCounter; 
 notifyChanged(); 

@Override
protected Parcelable onSaveInstanceState() {
 /*
 * Suppose a client uses this preference type without persisting. We
 * must save the instance state so it is able to, for example, survive
 * orientation changes.
 */
 
 final Parcelable superState = super.onSaveInstanceState();
 if (isPersistent()) {
 // No need to save instance state since it's persistent
 return superState;
 }
 
 // Save the instance state
 final SavedState myState = new SavedState(superState);
 myState.clickCounter = mClickCounter;
 return myState;
}
 
@Override
protected void onRestoreInstanceState(Parcelable state) {
 if (!state.getClass().equals(SavedState.class)) {
 // Didn't save state for us in onSaveInstanceState
 super.onRestoreInstanceState(state);
 return;
 }
 
 // Restore the instance state
 SavedState myState = (SavedState) state;
 super.onRestoreInstanceState(myState.getSuperState());
 mClickCounter = myState.clickCounter;
 notifyChanged();
}

 

其中SavedState為BaseSavedState的子類,這裡不詳細介紹瞭,而BaseSavedState實現瞭Parcelable接口,借用Windows平臺上的Serialable,其功能和其它平臺上序列化功能類似。

6. MyPreference響應Click事件,並將按鍵次數存入Shared Preferences 中。

[java]
@Override 
protected void onClick() { 
 int newValue = mClickCounter + 1; 
 // Give the client a chance to ignore this change if they deem it  
 // invalid  
 if (!callChangeListener(newValue)) { 
 // They don't want the value to be set  
 return; 
 } 
  
 // Increment counter  
 mClickCounter = newValue; 
  
 // Save to persistent storage (this method will make sure this  
 // preference should be persistent, along with other useful checks)  
 persistInt(mClickCounter); 
  
 // Data has changed, notify so UI can be refreshed!  
 notifyChanged(); 

@Override
protected void onClick() {
 int newValue = mClickCounter + 1;
 // Give the client a chance to ignore this change if they deem it
 // invalid
 if (!callChangeListener(newValue)) {
 // They don't want the value to be set
 return;
 }
 
 // Increment counter
 mClickCounter = newValue;
 
 // Save to persistent storage (this method will make sure this
 // preference should be persistent, along with other useful checks)
 persistInt(mClickCounter);
 
 // Data has changed, notify so UI can be refreshed!
 notifyChanged();
}

 

Preference使用persistBoolean, persistFloat ,persistInt, persistLong ,persisitString 向Shared Preferences中存儲數據,因為mClickCounter為整數,所以使用persistInt。 notifyChanged用於通知UI有數據變化。callChangeListener 將會調用註冊過的Preference.OnPreferenceChangeListener 以通知Preference有變化。

再來看看AdvancedPreferences,代碼不是很長,如下:

[java] 
public class AdvancedPreferences 
 extends PreferenceActivity 
 implements OnSharedPreferenceChangeListener { 
 public static final String KEY_MY_PREFERENCE 
 = "my_preference"; 
 public static final String KEY_ADVANCED_CHECKBOX_PREFERENCE 
 = "advanced_checkbox_preference"; 
  
 private CheckBoxPreference mCheckBoxPreference; 
 private Handler mHandler = new Handler(); 
  
 /**
 * This is a simple example of controlling a preference from code.
 */ 
 private Runnable mForceCheckBoxRunnable = new Runnable() { 
 public void run() { 
 if (mCheckBoxPreference != null) { 
 mCheckBoxPreference 
 .setChecked(!mCheckBoxPreference.isChecked()); 
 } 
  
 // Force toggle again in a second  
 mHandler.postDelayed(this, 1000); 
 } 
 }; 
  
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
  
 // Load the XML preferences file  
 addPreferencesFromResource(R.xml.advanced_preferences); 
  
 // Get a reference to the checkbox preference  
 mCheckBoxPreference 
 = (CheckBoxPreference)getPreferenceScreen() 
 .findPreference(KEY_ADVANCED_CHECKBOX_PREFERENCE); 
 } 
  
 @Override 
 protected void onResume() { 
 super.onResume(); 
  
 // Start the force toggle  
 mForceCheckBoxRunnable.run(); 
  
 // Set up a listener whenever a key changes  
 getPreferenceScreen() 
 .getSharedPreferences() 
 .registerOnSharedPreferenceChangeListener(this); 
 } 
  
 @Override 
 protected void onPause() { 
 super.onPause(); 
  
 // Unregister the listener whenever a key changes  
 getPreferenceScreen() 
 .getSharedPreferences() 
 .unregisterOnSharedPreferenceChangeListener(this); 
  
 mHandler.removeCallbacks(mForceCheckBoxRunnable); 
 } 
  
 public void onSharedPreferenceChanged( 
 SharedPreferences sharedPreferences, 
 String key) { 
 // Let's do something when my counter preference value changes  
 if (key.equals(KEY_MY_PREFERENCE)) { 
 Toast.makeText(this, "Thanks! You increased my count to " 
 + sharedPreferences.getInt(key, 0), 
 Toast.LENGTH_SHORT).show(); 
 } 
 } 
  

public class AdvancedPreferences
 extends PreferenceActivity
 implements OnSharedPreferenceChangeListener {
 public static final String KEY_MY_PREFERENCE
 = "my_preference";
 public static final String KEY_ADVANCED_CHECKBOX_PREFERENCE
 = "advanced_checkbox_preference";
 
 private CheckBoxPreference mCheckBoxPreference;
 private Handler mHandler = new Handler();
 
 /**
 * This is a simple example of controlling a preference from code.
 */
 private Runnable mForceCheckBoxRunnable = new Runnable() {
 public void run() {
 if (mCheckBoxPreference != null) {
 mCheckBoxPreference
 .setChecked(!mCheckBoxPreference.isChecked());
 }
 
 // Force toggle again in a second
 mHandler.postDelayed(this, 1000);
 }
 };
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 // Load the XML preferences file
 addPreferencesFromResource(R.xml.advanced_preferences);
 
 // Get a reference to the checkbox preference
 mCheckBoxPreference
 = (CheckBoxPreference)getPreferenceScreen()
 .findPreference(KEY_ADVANCED_CHECKBOX_PREFERENCE);
 }
 
 @Override
 protected void onResume() {
 super.onResume();
 
 // Start the force toggle
 mForceCheckBoxRunnable.run();
 
 // Set up a listener whenever a key changes
 getPreferenceScreen()
 .getSharedPreferences()
 .registerOnSharedPreferenceChangeListener(this);
 }
 
 @Override
 protected void onPause() {
 super.onPause();
 
 // Unregister the listener whenever a key changes
 getPreferenceScreen()
 .getSharedPreferences()
 .unregisterOnSharedPreferenceChangeListener(this);
 
 mHandler.removeCallbacks(mForceCheckBoxRunnable);
 }
 
 public void onSharedPreferenceChanged(
 SharedPreferences sharedPreferences,
 String key) {
 // Let's do something when my counter preference value changes
 if (key.equals(KEY_MY_PREFERENCE)) {
 Toast.makeText(this, "Thanks! You increased my count to "
 + sharedPreferences.getInt(key, 0),
 Toast.LENGTH_SHORT).show();
 }
 }
 
}

 

它實現瞭OnSharedPreferenceChangeListener,因此可以用來監聽MyPreference的變化。

Handler ,由於程序中需要從工作線程中更新Preference的值,而Preference為UI,不可以從工作線程中直接更新UI,Handler允許工作線程來更新UI,後續有詳細介紹。本例每隔1秒將Haunted preference 值變化一次(選中->不選->選中->不選 ..)

registerOnSharedPreferenceChangeListener,unregisterOnSharedPreferenceChangeListener用來為SharedPreferences 註冊一個總的Preference 變化事件處理代碼。本例中MyPreference變化時在屏幕上顯示當前按鍵的次數:

 
作者:mapdigit
 
 

發佈留言