天天看點

Android 關于Preference相關類的分析

系統裡設定Settings app 裡面是用

Preference

來做的,這些在其他app裡也有涉及,比如dialer的設定部分,關于

Preference

這裡涉及到一些類,以後會常用碰到的,做一個筆記記錄和分析一下。

  • Activity :

    PreferenceActivity

  • Fragment :

    PreferenceFragment

  • Preference :

    Preference

    多個子類

    SwitchPreference

    ,

    RadioButtonPreference

    ,

    SeekBarPreference

    ,

    DialogPreference

    ,

    RingtonePreference

    ,

    以及多個

    DialogPreference

    子類

    ListPreference

    ,

    MultiCheckPreference

    ,

    EditTextPreference

Preference

Preference這個類内部包含的成員變量有:

private Context mContext;
private PreferenceManager mPreferenceManager;
private PreferenceDataStore mPreferenceDataStore;
private long mId;
private OnPreferenceChangeListener mOnChangeListener;
private OnPreferenceClickListener mOnClickListener;
private CharSequence mTitle;
private int mTitleRes;
private CharSequence mSummary;
private int mIconResId;
private Drawable mIcon;
private Intent mIntent;
private String mFragment;
private Bundle mExtras;
private boolean mEnabled = true;
private boolean mSelectable = true
private OnPreferenceChangeInternalListener mListener;
private PreferenceGroup mParentGroup;
           

這些屬性都是在平時會用到了,一個标準的Preference最簡單就隻需要一個标題

mTitle

内部接口:

public interface OnPreferenceChangeListener {
     boolean onPreferenceChange(Preference preference, Object newValue);
  }
  public interface OnPreferenceClickListener {
     boolean onPreferenceClick(Preference preference);
  }
           

分别在Preference被點選和改變值的時候回調。

Preference在初始化的時候會預設加載系統layout,然後加載對應屬性。

成員方法:

protected View onCreateView(ViewGroup parent)
protected void onBindView(View view) 


           

資料存儲相關方法:

public Context getContext()
public SharedPreferences getSharedPreferences()
public SharedPreferences.Editor getEditor()
protected void notifyChanged()
public PreferenceManager getPreferenceManager()
protected boolean persistString(String value)
protected boolean persistInt(int value)
protected boolean persistFloat(float value)
protected boolean persistLong(long value)
           

存儲資料與普通Sharedpreference類似;

protected boolean persistBoolean(boolean value) {
    if (!shouldPersist()) {
        return false;
    }
    if (value == getPersistedBoolean(!value)) {
        return true;
    }
    PreferenceDataStore dataStore = getPreferenceDataStore();
    if (dataStore != null) {
        dataStore.putBoolean(mKey, value);
    } else {
        SharedPreferences.Editor editor = mPreferenceManager.getEditor();
        editor.putBoolean(mKey, value);
        tryCommit(editor);
    }
    return true;
}
protected boolean getPersistedBoolean(boolean defaultReturnValue) {
    if (!shouldPersist()) {
        return defaultReturnValue;
    }
    PreferenceDataStore dataStore = getPreferenceDataStore();
    if (dataStore != null) {
        return dataStore.getBoolean(mKey, defaultReturnValue);
    }
    return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
}
           

以上就是Preference裡比較有用的成員和方法;其餘的子類則是分别通過繼承和重載來改寫view和資料部分,以達到實作不同的功能。

下面以

DialogPreference

為例來分析它是如何實作點選Preference就彈出Dialog對話框的。

一進入DialogPreference就看到了一些和AlertDialog相關的成員。

private AlertDialog.Builder mBuilder;
    private CharSequence mDialogTitle;
    private CharSequence mDialogMessage;
    private Drawable mDialogIcon;
    private CharSequence mPositiveButtonText;
    private CharSequence mNegativeButtonText;
    private int mDialogLayoutResId;
    private Dialog mDialog;
           

其餘大部分成員方法都是用來設定和Dialog相關的方法,比如設定标題,設定内容等。

然後

@Override
    protected void onClick() {
        if (mDialog != null && mDialog.isShowing()) return;
        showDialog(null);
    }
           

最重要的是重載了onClick,在點選的時候彈出對話框。

由于

DialogPreference

是抽象類,是以在對話框關閉的方法裡留了空

protected void onDialogClosed(boolean positiveResult) {
    	//等待子類繼承
    }
           

一般子類繼承這個方法都是用來更新資料和重繪view。

在xml裡的屬性用法舉例,

<PreferenceScreen   //Preference集合,類似于Viewgroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
    android:title="@string/network_dashboard_title">
    
    <Preference
        android:key="mobile_network_settings"    		//key
        android:title="@string/network_settings_title"  //title
        android:summary="@string/summary_placeholder"	//summary
        android:icon="@drawable/ic_network_cell"		//icon
        android:persistent="false"						//是否可持久化存儲
        android:dependency="toggle_airplane"	
        android:entries="@array/tty_mode_entries"		//清單的names
        android:entryValues="@array/tty_mode_values"	//清單的values
        android:defaultValue="true"						//預設值
        android:fragment="com.android.settings.TetherSettings"	//點選後跳轉的fragment
        android:order="-15"
        settings:keywords="@string/keywords_more_mobile_networks"
        settings:userRestriction="no_config_mobile_networks"
        settings:useAdminDisabledSummary="true">
        <intent
            android:action="android.intent.action.MAIN"
            android:targetPackage="com.android.phone"
            android:targetClass="com.android.phone.MobileNetworkSettings"/>
    </Preference>
</PreferenceScreen>
           

PreferenceActivity

PreferenceActivity是繼承自ListActivity的,

ListActivity裡面維持了一個ListView,

這裡面的Listview是設定了内部成員監聽的。

private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View v, int position, long id)
    {
        onListItemClick((ListView)parent, v, position, id);
    }
};
    
mList.setOnItemClickListener(mOnClickListener);
           

是以PreferenceActivity的界面裡,即使不手動設定點選監聽,也是會有回調的,PreferenceActivity隻需要繼承onListItemClick這個方法就行。

PreferenceActivity是個抽象類,在它的Oncreate裡有許多預設設定,

其中關鍵點是建構Header,頭部條目。

if (!onIsHidingHeaders()) {
       onBuildHeaders(mHeaders);
   }
   public void onBuildHeaders(List<Header> target) {
       // Should be overloaded by subclasses
   }
           

資料部分需要子類去重寫,如果子類不重寫則PreferenceActivity就顯示為空。

重要構造方法:

public void loadHeadersFromResource(@XmlRes int resid, List<Header> target)
public void addPreferencesFromResource(int preferencesResId)
           

PreferenceActivity經典使用舉例:

在Dialer的setting界面,就是

DialerSettingsActivity

,繼承自

PreferenceActivity

;

@Override
  public void onBuildHeaders(List<Header> target) {
	...
    Header soundSettingsHeader = new Header();
    soundSettingsHeader.titleRes = R.string.sounds_and_vibration_title;
    soundSettingsHeader.fragment = SoundSettingsFragment.class.getName();
    soundSettingsHeader.id = R.id.settings_header_sounds_and_vibration;
    target.add(soundSettingsHeader);
    ...
}
           

比較明顯的就是,在這個界面裡看到的内容條目都是一個個Header,然後構成清單出現。

當然這些都是用代碼方式填充資料的。

另一個繼承的

PreferenceActivity

CallFeaturesSetting

,則是用

addPreferencesFromResource

@Override
   protected void onResume() {
       super.onResume();
       ...
       addPreferencesFromResource(R.xml.call_feature_setting);
       ...
   }
           

這兩個的差別就在于 一個是從xml,一個是從java代碼裡添加資料。

onBuildHeaders需要繼承,addPreferencesFromResource隻需要在Oncreate裡調用。

忘了說,這裡的

Header

PreferenceActivity

的内部類,屬性部分幾乎和Preference大緻相同。

不過和Preference的差別在于,Preference自帶view,而這裡的Header更純粹的屬于一個item。

以上就是

PreferenceActivity

相關部分,使用起來的特點就是這幾個方法,擷取資料部分,以及點選回調,其餘的就是點選事件處理了。

PreferenceFragment

PreferenceFragment在平時使用中更頻繁,比如在Setting裡,所有的界面都是一個空Activity靠加載不同的Fragment去排程。

按照生命周期來說與普通的Fragment沒差别。

public void onCreate
public View onCreateView
public void onViewCreated
public void onActivityCreated
public void addPreferencesFromIntent(Intent intent)
public void addPreferencesFromResource(@XmlRes int preferencesResId)
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
public Preference findPreference(CharSequence key)
private void bindPreferences()
           

是以使用起來也不會很麻煩。

接下來說說Setting中的那些

PreferenceFragment

在Setting裡,似乎所有的設定子項都是用的

PreferenceFragment

比如

  • 設定網絡的

    NetworkDashboardFragment

  • 連接配接裝置的

    ConnectedDeviceDashboardFragment

  • 應用通知的

    AppAndNotificationDashboardFragment

  • 電池管理的

    PowerUsageSummary

  • 顯示管理的

    DisplaySettings

  • 聲音管理的

    SoundSettings

  • 存儲管理的

    StorageDashboardFragment

  • 安全設定的

    SecuritySettings

  • 系統設定的

    SystemDashboardFragment

他們全都是

PreferenceFragment

的子類。

DashboardFragment extends SettingsPreferenceFragment
SettingsPreferenceFragment extends InstrumentedPreferenceFragment
InstrumentedPreferenceFragment extends ObservablePreferenceFragment
ObservablePreferenceFragment extends PreferenceFragment
           

NetworkDashboardFragment

為例看看是怎麼做的。

@Override
    protected int getPreferenceScreenResId() {
        return R.xml.network_and_internet;
    }
        @Override
    protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
        return buildPreferenceControllers(context, getLifecycle(), mMetricsFeatureProvider, this
                /* fragment */,
                this /* mobilePlanHost */);
    }
    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Lifecycle lifecycle, MetricsFeatureProvider metricsFeatureProvider, Fragment fragment,
            MobilePlanPreferenceHost mobilePlanHost) {
            ....
    }
           

這裡隻重載了兩個方法

  1. getPreferenceScreenResId

    擷取xml的id
  2. getPreferenceControllers

    得到所有找到的preference的控制器

在父類

NetworkDashboardFragment

裡,

AbstractPreferenceController

是用來替換控制的,

比如

@Override
    public boolean onPreferenceTreeClick(Preference preference) {
        Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
        // If preference contains intent, log it before handling.
        mMetricsFeatureProvider.logDashboardStartIntent(
                getContext(), preference.getIntent(), getMetricsCategory());
        // Give all controllers a chance to handle click.
        for (AbstractPreferenceController controller : controllers) {
            if (controller.handlePreferenceTreeClick(preference)) {
                return true;
            }
        }
        return super.onPreferenceTreeClick(preference);
    }
           

點選的時候用對應的controller去實作點選。

Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
      for (AbstractPreferenceController controller : controllers) {
          controller.displayPreference(screen);
      }
           

顯示的時候用對應的controller去顯示。

這樣做的好處就是解耦,讓代碼邏輯更清晰,友善拓展。

因為設定裡的設定項确實非常多,應該各自寫各自的,互相影響才小。

好了,基本上該寫的都寫完了,如果我們要自己寫Preference界面,可以用

PreferenceActivity

或者

PreferenceFragment

都行,他倆都是抽象類,需要子類繼承,在使用的時候直接寫個xml來使用,設定項也可以單獨去用不同的Preference控制。

繼續閱讀