Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android應用Preference相關及源碼淺析(Preference組件家族篇)

Android應用Preference相關及源碼淺析(Preference組件家族篇)

編輯:關於Android編程

1 前言

前一篇(點我閱讀前一篇《Android應用Preference相關及源碼淺析(SharePreferences篇)》)我們討論分析使用了Android的SharePreferences,相信看過的朋友都有了自己的感悟與理解,這一篇我們繼續乘熱打鐵來說說SharePreferences的衍生品—-Preference組件。

其實Preference組件大家一定不陌生,因為Android系統的Setting應用及我們市面上一些符合Android設計思想的應用的設置界面一般都會用它來實現,而且Google原生Android代碼中大量的使用了Preference組件。

簡單說,Preference組件其實就是Android常見UI組件與SharePreferences的組合封裝實現。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

2 Preference組件家族基礎

2-1 Preference相關基礎概念

既然要先說說Preference組件家族基礎,那不得不先簡單說說這些Preference組件間的關系,如下一張圖是基於API 22繪制的一副Preference組件繼承關系圖:

這裡寫圖片描述

怎麼樣?相比以前低版本的API來說,谷歌官方又增加了一些實用的Preference組件,不過遺憾的是這裡面有些是被hide掉的,有些是在com.android.internal.preference包下的,所以這些hide和com.android.internal.preference包的preference我們應用層是不能直接使用的。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrW9tMvE49a4tqi74cu1o6zV4tCp1+m8/rrNztLDx8a9yrHTw7XERWRpdFRleHS1yNfpvP663MDgy8ajrLb4ztLDx8a9yrHTw7XERWRpdFRleHS1yNfpvP7P1Mq+ysfSwMC109pBY3Rpdml0ebrNRnJhZ21lbnS1xKOsxMfDtNXiwO+1xFByZWZlcmVuY2XX6bz+ysfU9cO0z9TKvrXExNijvzwvcD4NCjxwPrTwsLi+zcrHUHJlZmVyZW5jZdfpvP7SstPQ19S8utLAwLXP1Mq+tcS/8rzco6zX7rOjvPu1xL7Nz/FQcmVmZXJlbmNlQWN0aXZpdHm1yKOsvt/M5b+0tPrC687Sw8e74beiz9bI58/Co7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> public abstract class PreferenceActivity extends ListActivity implements PreferenceManager.OnPreferenceTreeClickListener, PreferenceFragment.OnPreferenceStartFragmentCallback { ...... } public abstract class PreferenceFragment extends Fragment implements PreferenceManager.OnPreferenceTreeClickListener { ...... }

嘿嘿,明白了吧?其實Preference組件的使用及展示到Window的原理和普通EditText控件展示使用是類似的,因為Preference組件的顯示框架PreferenceActivity及PreferenceFragment都是從基本的Activity及Fragment繼承而來,只是針對Preference進行了二次封裝而已。

到此對Preference組件已經有一個基本的概念認識了,接下來我們就一步一步往下看。

2-2 常用Preference相關基礎組件屬性說明

我們可以發現,Preference組件家族的控件還是比較豐富的,這裡肯定不能一一介紹,所以還是代表性的說幾個使用頻率最高作為指引就行了,其他的用到時參考相關官方API或者源碼即可。

2-2-1 獨立控件Preference

所有Preference組件的基類,類似常見控件的TextView,一個單純的item,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

如下是基類Preference的相關屬性介紹:

attr description android:defaultValue 默認值。 android:dependency 設置此元素附屬於另一個元素,依賴的可用則當前元素也可用(enable),反之。 android:enabled 設置是否可用。 android:fragment 指定fragment。 android:icon 指定左側的圖標。 android:key 選項的名稱,也是用來存儲時唯一的key。 android:layout 給當前元素指定一個自定義布局。 android:order 偏好的順序。如果不指定,默認的順序將字母。 android:persistent 是否將其值存儲到共享SharePreferences。 android:selectable 設置是否可以選擇操作。 android:shouldDisableView 當enabled設置為false變暗,同時此屬性設置為false時disable但不變暗。 android:summary 摘要,配置的簡要說明,顯示在標題下面。 android:title 選項的標題,當沒有設置summary時自動垂直居中顯示。 android:widgetLayout 控件可調小部件的布局。是為一個優先選擇的布局,比如一個復選框選擇要指定一個自定義布局(注意:包括的只是復選框)在這裡。


關於基類Preference提供的方法這裡就不再詳細列出了,如需查看,具體翻牆點我。

2-2-2 獨立控件CheckPreference

CheckPreference類似常見控件的CheckBox,一個item,右側有一個CheckBox,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

如下是CheckPreference的相關屬性介紹:

attr description android:disableDependentsState 與android:dependency相反;B可用,則A不可用;B不可用,則A可用。 android:summaryOff 選項未選中時顯示的摘要。 android:summaryOn 選項被選中時顯示的摘要。

2-2-3 獨立控件EditTextPreference

EditTextPreference類似常見控件的EditText,一個item,點擊彈出一個EditText的對話框,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

該控件無自有屬性。具體不再介紹,相關方法查看官方API。

2-2-4 獨立控件ListPreference

ListPreference類似常見控件的ListView,一個item,點擊彈出一個ListView的Dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

如下是ListPreference的相關屬性介紹:

attr description android:entries list要顯示的item數組名字。 android:entryValues list要顯示的item數組值。

2-2-5 獨立控件MultiSelectListPreference

MultiSelectListPreference類似常見控件的ListView,一個item,點擊彈出一個多選的ListView的Dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

MultiSelectListPreference的相關屬性同上ListPreference。

2-2-6 獨立控件SwitchPreference

SwitchPreference類似常見控件的Switch,一個item,右側有一個Switch控件,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

如下是SwitchPreference的相關屬性介紹:

attr description android:disableDependentsState 與android:dependency相反;B可用,則A不可用;B不可用,則A可用。 android:summaryOff 選項未選中時顯示的摘要。 android:summaryOn 選項被選中時顯示的摘要。 android:switchTextOff 關閉狀態的文字提示。 android:switchTextOn 打開狀態的文字提示。

2-2-7 獨立控件RingtonePreference

RingtonePreference就是一個鈴聲選擇item,點擊彈出鈴聲選擇list的dialog,用於通過SharePreferences存儲操作的設置值,具體翻牆點我。

如下是RingtonePreference的相關屬性介紹:

attr description android:ringtoneType 鈴聲類型。ringtone/notification/alarm/all android:showDefault 選項中默認的鈴聲。 android:showSilent 是否顯示靜音項。

2-2-8 組合控件PreferenceScreen

PreferenceScreen就Preference hierarchy的root節點,實例化他可以使用createPreferenceScreen(Context)方法;這個類可以依附於兩個地方,當一個preferenceactivity指向他時用來作為根布局顯示偏好,當他嵌套出現在另一個Preference hierarchy內部時他會啟動一個新的界面來顯示子項Preference或者設置的intent;綜上也就是說它不僅可以作為設置界面顯示,而且還能夠啟動activity,具體翻牆點我。

如下展示了作為根布局及子布局的兩種情況:

<--! 作為根及子項展示設置界面>

    
    
        
        ... other preferences here ...
    
 
<--! 內嵌intent的模式>

    

2-2-9 組合控件PreferenceCategory

PreferenceCategory類似於LinearLayout,用於組合一組可設置標題的Preference,使布局更具備層次感,具體翻牆點我。

這個類也沒有啥特殊的東西介紹,詳細參考API。

到此常用的Preference組件xml屬性介紹完畢,對應的java方法就不再說明了,還有就是他們的protect方法也不再詳細介紹,具體參見API。

2-3 新增Headers相關基礎組件屬性說明

上面我們簡單介紹了PreferenceScreen相關xml的屬性,這些其實是老版本的處理方式;自從Android 3.0引入Fragment之後,Preference相關的控件也有了變化。

由於PreferenceActivity在3.0開始也需要能夠處理多屏幕碎片化問題,所以Android 3.0之前采用PreferenceScreen嵌套的方法來跳轉分類細則,而Android 3.0及之後使用了Preference Headers的方法來適配多屏幕碎片化問題。

他的核心就是在主屏中通過headers的xml布局列出所有的主題設置項,每個主題設置的詳細設置由各自指定的PreferenceFragment負責,而各自的PreferenceFragment可以如傳統的PreferenceActivity 一樣布局自身的PreferenceScreen。

preference-headers就是他們的root,既然這樣,那我們就來看看Headers相關的組件及方法吧。

2-3-1 PreferenceActivity.Header相關屬性方法使用基礎

點我翻牆查看。Header繼承自Object,實現了Parcelable,用來展示一個item的header。

相關屬性如下:

attr description android:icon   android:breadCrumbShortTitle 在fragment顯示的短標題文字。 android:breadCrumbTitle 在fragment顯示的標題文字。 android:fragment 當選擇該頭文件時,將顯示該fragment的全名稱。 android:id 唯一識別id。 android:summary item描述信息。 android:title item頭名稱。

如下是一個簡單展示:

      
      
     
 
......
......

關於Header的用法下面會詳細演示,基本情況就介紹到這裡。

2-4 Preference相關組件顯示操作控制API解釋

有了上面Preference組件基本概念及屬性介紹以後就相當於我們有了磚瓦,接下來就是咋蓋房子了,也就是如何組合這些組件顯示在屏幕上,我們現在就來看看這些常用的操作。

2-4-1 PreferenceActivity相關屬性方法使用基礎

翻牆點我查看。PreferenceActivity繼承自ListActivity,這個類是Preference相關控件展示的基類,在Android 3.0以前推薦直接使用,3.0以後推薦和preferencefragment一起使用,所以你可以看見PreferenceActivity中有些方法現在已經是過時的了。

首先看下PreferenceActivity加載xml目錄下的文件使用的方法,如下:

public class DemoActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List
target) { super.onBuildHeaders(target); //當大於等於3.0版本時推薦重寫該方法加載xml,headers+fragments模式 loadHeadersFromResource(R.xml.preference_header, target); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { //當小於3.0版本時推薦重寫該方法加載xml,當然大於時也可以用,只是不推薦而已 addPreferencesFromResource(R.xml.preference); } } }

如下我們來看看PreferenceActivity相關的常用方法:

method description public void addPreferencesFromIntent(Intent intent) @deprecated,添加一個匹配intent的preferences activity。 public void addPreferencesFromResource(int preferencesResId) @deprecated,添加一個xml到activity。 public Preference findPreference(CharSequence key) @deprecated,查找一個指定key的Preference。 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) 結束指定的fragment,參數返回類似activity。 public PreferenceManager getPreferenceManager() @deprecated,獲取activity使用的PreferenceManager實例。 public PreferenceScreen getPreferenceScreen() @deprecated,獲取當前activity的根布局視圖。 public boolean hasHeaders() 返回當前activity是否顯示了header list。 public void invalidateHeaders() 刷新已經顯示的header list,會重新回調onBuildHeaders()。 public boolean isMultiPane() 是否同時顯示headers和fragment。 public void loadHeadersFromResource(int resid, List target) 解析一個headers的xml然後添加到target列表裡。 public void onBuildHeaders(List target) 一般需要重寫,注意!這個函數可能不是總會被調用,例如,如果該Activity已被要求顯示一個特定的Fragment而不需要頭文件,就不需要構建Headers,所以不調運。 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, int titleRes, int shortTitleRes) 構造一個顯示Fragment的Intent對象。 public void onContentChanged() 當界面發生變化時回調。 public void onHeaderClick(PreferenceActivity.Header header, int position) 當選擇Headers列表項時調用,默認實現調用startwithfragment或switchtoheader。 public boolean onIsMultiPane() 大屏下默認實現是true。 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) 當單擊某個具有與它相關聯的gragment類名稱時調用。 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) @deprecated,當Preference控件被點擊時,觸發該方法。參數preference為點擊的對象,返回值true代表點擊事件已成功捕捉,無須執行默認動作或者返回上層調用,例如,不跳轉至默認Intent。 public void setListFooter(View view) 給Headers list設置foot view。 public void startPreferenceFragment(Fragment fragment, boolean push) 起一個fragment,push決定是否入棧。 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) 依據是否multi-pane模式啟動一個preference的fragment(如果是小屏會重啟一個activity顯示)。 public void startWithFragment(……) 啟動一個新的fragment。 public void switchToHeader(……) 在大屏multi-pane模式下切換到fragment顯示給定參數的fragment。

 

2-4-2 PreferenceFragment相關屬性方法使用基礎

翻牆點我查看。PreferenceFragment繼承自Fragment,這個類是3.0以後推薦使用的,用來處理碎片化問題。

該類的常用方法和上面PreferenceActivity的介紹差不多,這裡不再詳細說明,只是PreferenceActivity的@deprecated方法在PreferenceFragment中不是@deprecated的而已。

2-4-3 PreferenceManager相關方法使用基礎

翻牆點我查看。PreferenceManager繼承自Object,這個類其實我們前一篇《Android應用Preference相關及源碼淺析(SharePreferences篇)》獲取Preference實例就該說明的,這裡才說而已。

Android中得到SharedPreference的方式有四種:

ContextWrapper.getSharedPreferences(String name, int mode)
可以自己設置SharedPreference的名字與模式。

Activity.getPreferences(int mode)
name是Activity名字,不能設置。

PreferenceManager.getSharedPreferences()
通過PreferenceManager維護一個SharedPreference,我們可以調用PreferenceManager的API來設置name和mode,並且最終也是調用到ContextWrapper的getSharedPreferences。

PreferenceManager.getDefaultSharedPreferences(Context context)
得到的SharedPreference是某個包名下共享私有的,不能讓其他的包訪問,而且name和mode不能設置,最終也會調用到ContextWrapper的getSharedPreferences。

接下來簡單看下PreferenceManager相關方法,如下:

method description PreferenceManager.OnActivityDestroyListener 當所依賴的activity銷毀時回調接口。 PreferenceManager.OnActivityResultListener 當所依賴的activity得到返回result時回調接口。 PreferenceManager.OnActivityStopListener 當所依賴的activity停止時回調接口。 public Preference findPreference(CharSequence key) 通過key找到Preference。 public static SharedPreferences getDefaultSharedPreferences(Context context) 每個應用有一個默認的preferences文件,通過該方法獲取。 public SharedPreferences getSharedPreferences() 通過PreferenceManager維護一個SharedPreference,可以調用PreferenceManager的API來設置name和mode。 public int getSharedPreferencesMode() 獲取當前的mode。 public String getSharedPreferencesName() 獲取當前的name。 public static void setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain) 更加靈活的設置默認值,注意readAgain參數。 public static void setDefaultValues(Context context, int resId, boolean readAgain) 設置默認值,注意readAgain參數。 public void setSharedPreferencesMode(int sharedPreferencesMode) 設置當前的mode。 public void setSharedPreferencesName(String sharedPreferencesName) 設置當前的name。


可以看見,這個類其實也沒啥介紹的,重點關注下setDefaultValues的幾個核心參數就行。如果我們的設置項很多,而且每項在代碼中都需要設置默認缺省值,那就推薦使用setDefaultValues方法。在應用第一次運行時,從preference的xml中獲取缺省值,並生成文件保存(如果已經有一個SharedPrefferences對象,也會進行更新,就像下面代碼中三四行對調);不是第一運行就不會改現有保存值。

protected void onCreate(Bundle savedInstanceState) {
    ......
    PreferenceManager.setDefaultValues(this, R.xml.default_value, false);
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 
    String option = prefs.getString(key, null); 
}

好了,控件使用就到這裡了。

2-5 Preference控件家族實例

關於Preference控件家族的使用比較簡單,自定義網上也一大把,所以不再給出例子。如果你想看例子可以參考如下:

官方Settings設計原理。

Settings源碼。

其他的相關用法參考API及網絡例子。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

3 Preference組件源碼設計簡單分析

扯蛋了這麼多,唉,歎個氣繼續吧,接下來就到了有意思的環節,源碼結構簡介。這裡只是針對Preference控件特性介紹分析,不會過多追究View及Activity和Fragment細節,具體View及Activity和Fragment細節後面會寫文章分析的。

3-1 PreferenceFragment源碼淺析

首先還記得上面基礎說了,PreferenceFragment使用第一步就是使用其內部方法addPreferencesFromResource或者addPreferencesFromIntent設置源。所以這裡我們以addPreferencesFromResource為例來說明,如下源碼:

//PreferenceFragment的方法
public void addPreferencesFromResource(int preferencesResId) {
    //判斷異常說明了該方法至少得在super.onCreate方法之後調運,以便初始化PreferenceManager
    requirePreferenceManager();
    //這個前面也介紹過的,設置根布局PreferenceScreen
    setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
            preferencesResId, getPreferenceScreen()));
}

接著我們看下setPreferenceScreen方法源碼,如下:

public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
    //設置根布局到PreferenceManager裡
    if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
        //空方法
        onUnbindPreferences();
        //設置標記,在onActivityCreated方法中有用
        mHavePrefs = true;
        //決定是否重設bind布局,核心都是為了執行bindPreferences方法
        if (mInitDone) {
            postBindPreferences();
        }
    }
}

到此接下來就是bind了,至於在這裡通過Handler發消息bindPreferences還是在onActivityCreated自動調bindPreferences方法取決於你把addPreferencesFromResource方法寫在那個生命周期方法裡。如下我們直接來看bindPreferences方法,如下源碼:

//這個方法是搭建顯示的核心方法!!!!!!!!!
private void bindPreferences() {
    //拿到PreferenceManager中存的根視圖PreferenceScreen
    final PreferenceScreen preferenceScreen = getPreferenceScreen();
    if (preferenceScreen != null) {
        //傳遞當前ListView到preferenceScreen的bind方法
        preferenceScreen.bind(getListView());
    }
    //PreferenceFragment的空方法
    onBindPreferences();
}

到此可以看見PreferenceFragment裡bind最終是交給了PreferenceScreen的bind來關聯PreferenceFragment的ListView與PreferenceScreen的ListAdapter。我們現在就來看下PreferenceScreen的bind源碼,如下:

//PreferenceScreen類的方法
public void bind(ListView listView) {
    //設置listview的item監聽
    listView.setOnItemClickListener(this);
    //PreferenceScreen中bind的重點核心!!!!!!!!!!!!!給listview設置adapter
    listView.setAdapter(getRootAdapter());
    //一些register操作,忽略
    onAttachedToActivity();
}

好了,我們還是來關注這個adapter咋來的吧,如下就是getRootAdapter方法源碼:

public ListAdapter getRootAdapter() {
    if (mRootAdapter == null) {
        mRootAdapter = onCreateRootAdapter();
    }

    return mRootAdapter;
}

protected ListAdapter onCreateRootAdapter() {
    return new PreferenceGroupAdapter(this);
}

終於真相快要大白了,PreferenceFragment的listview設置的adapter原來是PreferenceGroupAdapter。哈哈,我們繼續來看看這個類,如下:

//hide類,專門用來Preference的list顯示的adapter
public class PreferenceGroupAdapter extends BaseAdapter
        implements OnPreferenceChangeInternalListener {
    //省略相關屬性定義
    ......
    //構造方法,傳入的是PreferenceScreen根布局
    public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
        ......
        //sync設置相關list列表數據後通知listview刷新
        syncMyPreferences();
    }

    private void syncMyPreferences() {
        ......
        //通知listview刷新當前准備的Preference列表
        notifyDataSetChanged();
        ......
    }
    //省略一堆方法
    ......
    //notifyDataSetChanged後和普通adapter一樣item繪制會回調getView方法
    public View getView(int position, View convertView, ViewGroup parent) {
        //拿到當前item的Preference組件
        final Preference preference = this.getItem(position);
        ......
        //調運Preference的getView方法得到當前item真正的view顯示,這是核心!!!!!!!!!!!!
        //關於Preference的getView方法下面分析Preference源碼會說到的,或者你可以直接跳到Preference源碼分析部分查看。
        View result = preference.getView(convertView, parent);
        ......
        return result;
    }
    ......
}

到此你會發現,其實無非就是ListView和Adapter的關系,而Adapter的getView所得到的View由Preference提供而已,而Adapter由PreferenceScreen管理而已。

3-2 PreferenceActivity源碼淺析

說到PreferenceActivity現在不推薦的addPreferencesFromResource方法時其實是沒啥解釋的,這種模式現在被官方推薦通過PreferenceFragment的addPreferencesFromResource來實現,所以也就是說關於PreferenceActivity的addPreferencesFromResource方法(也就是在PreferenceActivity中直接添加Preference組件)其顯示原理和上面分析的PreferenceFragment是一樣的,所以這裡就不再過多解釋了。

我們把重點放在loadHeadersFromResource方法上,也就是現在推薦的PreferenceActivity放置Headers模式。接下來就來分析分析吧。

public abstract class PreferenceActivity extends ListActivity implements
        PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragment.OnPreferenceStartFragmentCallback {
    ......
    //省略一堆方法
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //設置基礎布局
        setContentView(com.android.internal.R.layout.preference_list_content);
        //獲取一些ContentView裡的控件實例
        ......
        //判斷是啥模式,左右展示還是單頁
        boolean hidingHeaders = onIsHidingHeaders();
        mSinglePane = hidingHeaders || !onIsMultiPane();
        //獲取fragment參數(其實是PreferenceActivity中點擊Header item重啟PreferenceActivity時傳遞的)
        String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
        Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
        int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
        int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);

        if (savedInstanceState != null) {
            ......  //忽略,非重點主線
        } else {
            if (initialFragment != null && mSinglePane) {
                //SinglePane時有參數則替換顯示Fragment
                switchToHeader(initialFragment, initialArguments);
                ......
            } else {
                //核心方法之一!!!!!!!!!!!!
                //記得上面基礎使用介紹過嗎?新的實現重寫onBuildHeaders空方法,在其中
                //調運loadHeadersFromResource方法加載header list xml文件
                onBuildHeaders(mHeaders);
                //如果存在header list則走這裡(上面onBuildHeaders裡會組織生成mHeaders的list結構)
                if (mHeaders.size() > 0) {
                    //header-fragment左右各半屏模式
                    if (!mSinglePane) {
                        if (initialFragment == null) {
                            //設置顯示header
                            Header h = onGetInitialHeader();
                            switchToHeader(h);
                        } else {
                            //設置顯示header及fragment
                            switchToHeader(initialFragment, initialArguments);
                        }
                    }
                }
            }
        }

        if (initialFragment != null && mSinglePane) {
            //當SinglePane加載的是Fragment時隱藏header,顯示fragment
            findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
            mPrefsContainer.setVisibility(View.VISIBLE);
            ......
        } else if (mHeaders.size() > 0) {
            //重點!!!!!!!!!!!!!!!!!這就是要分析的header的listview的adapter放置地
            setListAdapter(new HeaderAdapter(this, mHeaders));
            ......
        } else {
            //這就是最原始的供已經不推薦的addPreferencesFromResource方式加載Preference組件了
            //具體原理同上PreferenceFragment的加載顯示原理了,不再分析
            setContentView(com.android.internal.R.layout.preference_list_content_single);
            ......
        }
        //其他初始設置
        ......
    }
}

通過上面的分析可以看見其實對於Header的adapter核心就是setListAdapter(new HeaderAdapter(this, mHeaders));這句代碼。那我們就來看看這個內部類HeaderAdapter,源碼如下:

//可以發現PreferenceActivity的內部類HeaderAdapter是繼承自ArrayAdapter的,
//這個Adapter就是用來給推薦的Header list的listview提供數據的。
private static class HeaderAdapter extends ArrayAdapter
{ //Holder裡只有最典型經典的三個組件 private static class HeaderViewHolder { ImageView icon; TextView title; TextView summary; } private LayoutInflater mInflater; //構造方法,不解釋 public HeaderAdapter(Context context, List
objects) { super(context, 0, objects); mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } //最最核心方法!!!!!!Header list被顯示到PreferenceActivity的listview關鍵點 @Override public View getView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; View view; //再常見不過的Adapter數據加載ViewHolder寫法了 if (convertView == null) { //加載header的item布局,都是用的preference_header_item文件,如下會介紹 view = mInflater.inflate(com.android.internal.R.layout.preference_header_item, parent, false); holder = new HeaderViewHolder(); holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); view.setTag(holder); } else { view = convertView; holder = (HeaderViewHolder) view.getTag(); } //一堆顯示,通過getItem(position)拿到構造裡傳入的List
類型objects的item // All view fields must be updated every time, because the view may be recycled Header header = getItem(position); holder.icon.setImageResource(header.iconRes); holder.title.setText(header.getTitle(getContext().getResources())); CharSequence summary = header.getSummary(getContext().getResources()); if (!TextUtils.isEmpty(summary)) { holder.summary.setVisibility(View.VISIBLE); holder.summary.setText(summary); } else { holder.summary.setVisibility(View.GONE); } return view; } }

可以看見這個adapter的getView中的item核心是加載了一個preference_header_item的xml文件,然後設置作為item的header。這個xml源碼如下:




    

    

        

        

    

哈哈,到此就不在解釋啥了,很直觀了,就是這麼任性,就是這麼簡單的實現了Header List的顯示。

3-3 Preference源碼淺析

說這個的原因是上面PreferenceFragemnt分析加載設置adapter的getView方法時留下的歷史問題。我們先來看看這個文件的核心代碼,後面總結串起來你就明白了,如下源碼:

//可以看見,他不是一個View,但是組合管理了一個View和PreferenceManager
public class Preference implements Comparable {
    ......
    //各種屬性
    ......
    private PreferenceManager mPreferenceManager;
    //重點關注,和自定義及Preference顯示原理息息相關,preference就是下面列出的xml資源
    private int mLayoutResId = com.android.internal.R.layout.preference;
    private int mWidgetLayoutResId;

    ......
    //各種getXXX及setXXX方法
    ......

    /**
     * Gets the View that will be shown in the {@link PreferenceActivity}.
     * 獲取Preference的item顯示view
     */
        public View getView(View convertView, ViewGroup parent) {
                if (convertView == null) {
                convertView = onCreateView(parent);
                }
                onBindView(convertView);
                return convertView;
        }

    protected View onCreateView(ViewGroup parent) {
        final LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final View layout = layoutInflater.inflate(mLayoutResId, parent, false); 
        final ViewGroup widgetFrame = (ViewGroup) layout.findViewById(com.android.internal.R.id.widget_frame);
        if (widgetFrame != null) {
            //mWidgetLayoutResId有專門的set方法可以設置或者重寫
            if (mWidgetLayoutResId != 0) {
                //android:id/widget_frame為mWidgetLayoutResId所對應的布局預留空間插入
                layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
            } else {
                //默認實現是null的
                widgetFrame.setVisibility(View.GONE);
            }
        }
        return layout;
    }

    /**
         * Binds the created View to the data for this Preference.
         *

* This is a good place to grab references to custom Views in the layout and * set properties on them. *

*/ protected void onBindView(View view) { //設置子View相關屬性 final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title); if (titleView != null) { final CharSequence title = getTitle(); if (!TextUtils.isEmpty(title)) { titleView.setText(title); titleView.setVisibility(View.VISIBLE); } else { titleView.setVisibility(View.GONE); } } ...... //類似的各種子View設置操作,不再列出 } ...... }

可以看見,這個getView其實就是上面PreferenceFragment分析中Adapter中getView調運的Preference的getView。怎麼樣,串起來吧。也就是說Preference不是View,但是他提供View給ListView的每一個Item顯示,其提供的View的基類布局(上面Preference類中mLayoutResId屬性的值)如下:




    

    

        

        

    

    
    

哈哈,不用解釋了吧,這下相信你可以將前面基礎使用和源碼分析幾部分完全串起來理解了吧。

3-4 Preference組件家族源碼分析總結

通過上面分析可以知道Preference其實不是View,但是其內部創建管理了一個View(ListView的item,被Adapter的getView通過Preference.getView方法獲得顯示)。

可以看出來,上面我們分析Preference的onCreateView、getView、onBindView這幾個方法其實是整個Preference組件顯示等的核心方法,所以正如系統提供的Preference的各種實用子類一樣,當我們想自定義Preference的時候完全可以重寫這些方法來得到自己的各種自定義View,這樣就完美的解決了代碼的擴展性,我們不用去修改ListAdapter的實現就能實現自定義的Preference,所以說可見Google的工程師在設計Preference結構時是多麼的牛叉,不得不膜拜。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

4 Preference組件家族總結

題外話:其實這篇文章是受我一個朋友邀請幫忙寫的。還記得去年年初我在上家公司(做Android盒子)負責修改一個項目的Settings源碼,添加一個屏幕縮放功能在Settings裡面。後來做好以後維護轉手給了別人,當時別人是個新手,各種問。所以落下後遺症,於是乎就在邀請之下打算寫了這一系列兩篇文章,以幫助快速上手原生Settings的修改。

其實沒啥總結的,還記得前段時間在網上看見有人吐槽Preference是google設計的一個失敗品,一點也不好用啥的。其實我想說Preference的設計還是不錯的,是值得借鑒的,隨便舉個例子如下:

在布局設計上可以保持統一預留差異區域供自定義動態插入,達到復用的目的。

在實現listView各個item不同的Adapter的getView方法時不用像傳統那樣if-else或者switch操作,而是預留一個基類用於實現回調,這樣更加靈活。

其他的慢慢體會就行了。

所以說Preference的設計還是非常牛逼靈活的,對那些在網上扯蛋不好用的人說No!!!

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved