Preference組件探究之源碼解讀

上一篇文章的DEMO中我們只是使用PreferenceActivity#addPreferencesFromResource(),系統就將設置頁面展示出來了,至始至終我們沒有跟View直接接觸。父類到底幫我們做了什麼,如果我們要自己控制View的實例可以嗎?其大致的原理是什麼呢。

我們將從源碼入手,逐步探究背後的原理。以下代碼版本爲Android OREO。

先看下PreferenceActivity#addPreferencesFromResource()的實現邏輯。


PreferenceActivity#addPreferencesFromResource()

public abstract class PreferenceActivity extends ListActivity…{
     …
    public void addPreferencesFromResource(int preferencesResId) {
        requirePreferenceManager();

        setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
                getPreferenceScreen()));
    }

    private void requirePreferenceManager() {
        if (mPreferenceManager == null) {
            if (mAdapter == null) {
                throw new RuntimeException("This should be called after super.onCreate.");
            }
            throw new RuntimeException(
                    "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
        }
    }
    …
}

在執行setPreferenceScreen()前先調用requirePreferenceManager()確保mPreferenceManager和mAdapter不爲空。否則將拋出異常。

我們在使用的時候並沒有發生上述異常,那麼這個mPreferenceManager和和mAdapter是在哪創建的?又是幹什麼用的呢?

public abstract class PreferenceActivity extends ListActivity…{
    private PreferenceManager mPreferenceManager;★
    …
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        …
        if (mHeaders.size() > 0) {
            setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
                    mPreferenceHeaderRemoveEmptyIcon)); 
            if (!mSinglePane) {
                getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);★②
            }
        }
	…
        if (mHeaders.size() == 0 && initialFragment == null) {
            …
setContentView(com.android.internal.R.layout.preference_list_content_single);
            mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
            mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
            mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); ★①
            mPreferenceManager.setOnPreferenceTreeClickListener(this);
            mHeadersContainer = null;
        }
        …
    }
}

我們發現mPreferenceManager是定義在PreferenceActivity的全局變量並在★①的onCreate()裏進行初始化。
PreferenceManager在源碼中的如下備註表示該類用於從activities或者xml中創建Preference組件的幫助類。

// PreferenceManager.java
/**
* Used to help create {@link Preference} hierarchies
* from activities or XML.
* <p>
* In most cases, clients should use
* {@link PreferenceActivity#addPreferencesFromIntent} or
* {@link PreferenceActivity#addPreferencesFromResource(int)}.
*
* @see PreferenceActivity
*/

而mAdapter則是定義在父類ListActivity中的ListAdapter實例,是在★②onCreate()中通過調用setListAdapter()進行初始化的。

public class ListActivity extends Activity {
    /**
     * This field should be made private, so it is hidden from the SDK.
     * {@hide}
     */
    protected ListAdapter mAdapter;
    …
}

mPreferenceManager和mAdapter實例是後續操作所依賴的處理,所以在執行關鍵處理前需要確保其不爲空。

兩個實例都是在onCreate()裏進行初始化的,也啓發我們必須在父類的onCreate()執行後再調用addPreferencesFromResource()處理。

接着看後續處理。

調用setPreferenceScreen()前先調用PreferenceManager#inflateFromResource()初始化PreferenceScreen實例。
※getPreferenceScreen()實際從PerferenceManager中取得當前頁面的PreferenceScreen實例,此時尚且爲null。

PreferenceManager#inflateFromResource()

public class PreferenceManager {
    …
    public PreferenceScreen inflateFromResource(Context context, @XmlRes int resId,
            PreferenceScreen rootPreferences) {
        // Block commits
        setNoCommit(true);

        final PreferenceInflater inflater = new PreferenceInflater(context, this);
        rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
        rootPreferences.onAttachedToHierarchy(this);

        // Unblock commits
        setNoCommit(false);

        return rootPreferences;
    }
    …
}

通過創建Preference組件填充類PreferenceInflater的實例去解析指定的Preference佈局。實際調用的是父類GenericInflater的相關邏輯。

abstract class GenericInflater<T, P extends GenericInflater.Parent> {
    public T inflate(XmlPullParser parser, P root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            mConstructorArgs[0] = mContext;
            T result = (T) root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != parser.START_TAG
                        && type != parser.END_DOCUMENT) {
                    ;
                }

                if (type != parser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                …
                // Temp is the root that was found in the xml
                T xmlRoot = createItemFromTag(parser, parser.getName(),
                        attrs);

                result = (T) onMergeRoots(root, attachToRoot, (P) xmlRoot);
                …
                // Inflate all children under temp
                rInflate(parser, result, attrs);
                …
            }
            …
            return result;
        }
    }

    private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs)
            throws XmlPullParserException, IOException {
        final int depth = parser.getDepth();

        int type;
        while (((type = parser.next()) != parser.END_TAG || 
                parser.getDepth() > depth) && type != parser.END_DOCUMENT) {

            if (type != parser.START_TAG) {
                continue;
            }

            if (onCreateCustomFromTag(parser, parent, attrs)) {
                continue;
            }

            if (DEBUG) {
                System.out.println("Now inflating tag: " + parser.getName());
            }
            String name = parser.getName();

            T item = createItemFromTag(parser, name, attrs);

            if (DEBUG) {
                System.out
                        .println("Creating params from parent: " + parent);
            }

            ((P) parent).addItemFromInflater(item);

            if (DEBUG) {
                System.out.println("-----> start inflating children");
            }
            rInflate(parser, item, attrs);
            if (DEBUG) {
                System.out.println("-----> done inflating children");
            }
        }

    }
    …
}

inflate()獲取XML的根節點即PreferenceScreen,獲取標籤名稱調用createItemFromTag()和createItem()創建對應的PreferenceScreen實例。

接着調用rInflate(),遞歸遍歷xml節點並逐個調用createItemFromTag()和createItem()創建對應的子節點實例。

然後通過調用PerferenceGroup#addItemFromInflater()將子節點追加到所屬的PerferenceGroup(PreferenceCategory或PreferenceScreen)中。

PerferenceGroup#addPreference()

public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
    …
    public void addItemFromInflater(Preference preference) {
        addPreference(preference);
    }

    public boolean addPreference(Preference preference) {
        //每個PreferenceGroup對象都內持存放Preference對象的ArrayList。
        if (mPreferenceList.contains(preference)) { // 檢查是否已經存在
            // Exists
            return true; // Group內已經包含了該Preference對象的話將駁回
        }

        // 判斷該Preference是否已經指定了order數值
        if (preference.getOrder() == Preference.DEFAULT_ORDER) {
            // 判斷Group是否被更改了mOrderingAsAdded的標誌。
            // 默認爲true,表示按照Preference的添加順序進行排序
            // 可以通過orderingFromXml標籤指定或setOrderingAsAdded()修改
            if (mOrderingAsAdded) {
                // Preference沒指定order,Group也是默認的order計數的場合
                // 給改Preference手動設置計算後的order值(從0開始累加)
                preference.setOrder(mCurrentPreferenceOrder++);
            }

            // 待嵌套的Preference也是Group的話,將當前的順序flag覆蓋過去
            if (preference instanceof PreferenceGroup) {
            ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded);
            }
        }

        // 告知Preference上級Preference即將變化,默認返回true
        // 意圖是給予Preference攔截這種變化,表示自己不想被添加到Group
        if (!onPrepareAddPreference(preference)) {
            return false;
        }

        synchronized(this) {
            // Preference列表中二分法查找待追加Preference
            // 過程中將調用到Preference#compareTo()的邏輯去和已有的Prefrence進行比較
            // 此刻列表中肯定不存在目標Preference,binarySearch將返回待插入index + 1的負值
            int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
            if (insertionIndex < 0) {
                // 取得待插入位置
                insertionIndex = insertionIndex * -1 - 1;
            }

            // 將目標Preference插入目標位置
            // 爲何不直接add而是先查找再添加至執行位置?
            // 答:Preference指定了order的場合,該數值不一定比最後一個元素的order值(從0開始)大,有可能介於list中間某處,所以最好用二分法查找找到合適的位置再插入
            mPreferenceList.add(insertionIndex, preference);
        }

        // 告知Preference對象已經被添加到Preference組件中。
        // ① 共享PreferenceManager實例給Preference:調用Preference#getPreferenceManager() 獲取Group自己被追加到上級後持有的PreferenceManager實例傳遞給Preference
        // ② 將該Preference在PreferenceManager中的ID值保存
        // ③ 初始化Preference的默認值:調用dispatchSetInitialValue()->onSetInitialValue()將defaultValue標籤或者setDefaultValue()配置的初始值反映
        preference.onAttachedToHierarchy(getPreferenceManager());

        // 初始化Preference持有的表示parent的Group變量
        preference.assignParent(this);

        // 判斷此刻Group是否已經添加到Activity上去了
        // true則調用Preference的同名函數
        // 初次添加的場合,默認false;當addPrefereneFromResource已經調用完畢後手動調用PreferenceGroup#addPreference()時該flag即爲true。
        if (mAttachedToActivity) {
            preference.onAttachedToActivity();
        }

        // 調用Preference#notifyHierarchyChanged()以回調層級變化的接口。
        notifyHierarchyChanged();

        return true; // 至此返回true表示添加成功
    }
    …
    protected boolean onPrepareAddPreference(Preference preference) {
        preference.onParentChanged(this, shouldDisableDependents());
        return true;
    }
}

至此,已經成功地解析了Preference的xml文件,將對應的Preference組件全部實例化並按照嵌套關係相互進行了綁定最後得到一個根組件PreferenceScreen的實例。

在返回PreferenceScreen的實例前調用Preferences.onAttachedToHierarchy()將PreferenceManager的實例及作爲Preference在Manager中的ID反映PreferenceScreen中。

接下來繼續剖析PreferenceActivity#setPreferenceScreen()的邏輯。

PreferenceActivity#setPreferenceScreen()

public abstract class PreferenceActivity extends ListActivity…{
    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
        // 再次確保mPreferenceManager和mAdapter實例不爲空
        requirePreferenceManager();

        // 將得到的PreferenceScreen實例更新到PreferenceManager中。
        // 已有PreferenceScreen實例且兩者相同的話將返回false。
        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
            // 得到的PreferenceScreen實例不爲空且不同於已有實例的場合

            // 向PreferenceActivity內置主線程Handler發送MSG_BIND_PREFERENCES消息,接着調用bindPreferences()
            postBindPreferences();

            CharSequence title = getPreferenceScreen().getTitle();
            // Set the title of the activity
            if (title != null) {
                setTitle(title);
            }
        }
    }
    …
    private void bindPreferences() {
        // 從持有的PreferenceManager實例中取得持有的PreferenceScreen實例
        final PreferenceScreen preferenceScreen = getPreferenceScreen();

        if (preferenceScreen != null) {
            // 先調用父類ListActivity#getListView()取得ListView實例。
            // 再調用PreferenceScreen#bind()將自己和ListView綁定。
            preferenceScreen.bind(getListView());
            if (mSavedInstanceState != null) {
                super.onRestoreInstanceState(mSavedInstanceState);
                mSavedInstanceState = null;
            }
        }
    }
}

我們簡單提及下ListView的實例是怎麼獲取到的。

ListActivity#getListView()

public class ListActivity extends Activity {
…
    public ListView getListView() {
        ensureList(); // 確保ListView實例已經初始化並返回ListView實例
        return mList;
    }

    private void ensureList() {
        // 如果已經初始化則什麼也不做,實際上PreferenceActivity#onCreate()已經加載過了ListView的佈局。
        if (mList != null) {
            return;
        }
        // 否則加載默認的包含ListView控件的佈局。
setContentView(com.android.internal.R.layout.list_content_simple);
    }
}

public abstract class PreferenceActivity extends ListActivity…{
     …
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Theming for the PreferenceActivity layout and for the Preference Header(s) layout
        TypedArray sa = obtainStyledAttributes(null,
                com.android.internal.R.styleable.PreferenceActivity,
                com.android.internal.R.attr.preferenceActivityStyle,
                0);

        // 獲取到style下PreferenceActivity_layout指定的佈局文件id,默認將返回指定的默認佈局即FW裏preference_list_content.xml。
        final int layoutResId = sa.getResourceId(
                com.android.internal.R.styleable.PreferenceActivity_layout,
                com.android.internal.R.layout.preference_list_content); 

        mPreferenceHeaderItemResId = sa.getResourceId(
                com.android.internal.R.styleable.PreferenceActivity_headerLayout,
                com.android.internal.R.layout.preference_header_item);
        mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
                com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
                false);

        sa.recycle();

        setContentView(layoutResId);★
        …
    }
    …
}

★可以看出PreferenceActivity在onCreate()裏給自己指定了包含ListView的佈局以保證ListActivity能夠持有關鍵的ListView實例。

ListView控件的實例得到了,接下來看系統如何將ListView和PreferenceScreen這個抽象的概念聯繫到了一起。

PreferenceScreen#bind()

public final class PreferenceScreen extends PreferenceGroup…{
    public void bind(ListView listView) {
        // 將ListView的item點擊回調設置爲自己。
        listView.setOnItemClickListener(this);

        // 調用getRootAdapter()作成Adapter實例並設置給ListView去展示。
        listView.setAdapter(getRootAdapter());
        
        onAttachedToActivity();
    }

    public ListAdapter getRootAdapter() {
        if (mRootAdapter == null) {
            // 實際上就是獲取PreferenceGroupAdapter實例。
            mRootAdapter = onCreateRootAdapter();
        }
        
        return mRootAdapter;
    }

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

 PreferenceGroupAdapter#PreferenceGroupAdapter()

public class PreferenceGroupAdapter extends BaseAdapter… {
    public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
        mPreferenceGroup = preferenceGroup;
    
        // 設置PreferenceScreen的層級變化回調。
        mPreferenceGroup.setOnPreferenceChangeInternalListener(this);

        // 創建Preference列表。
        mPreferenceList = new ArrayList<Preference>();

        // 創建PreferenceLayout列表。
        mPreferenceLayouts = new ArrayList<PreferenceLayout>();

        // 初始化Preference列表並反映到View視圖中。
        syncMyPreferences();
    }

    private void syncMyPreferences() {
        synchronized(this) {
            if (mIsSyncing) {
                return; // 如果已經在sync中駁回。
            }

            mIsSyncing = true;
        }

        List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());

        // 從PreferenceScreen對象中取得Preference列表。
        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
        mPreferenceList = newPreferenceList;
        
        // 通知視圖刷新。
        notifyDataSetChanged();

        synchronized(this) {
            mIsSyncing = false; // 結束sync並將flag重置。
            notifyAll();
        }
    }

    private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
        // PreferenceGroup#sortPreferences()調用Collections去將PreferenceScreen內部持有的Preference列表排序。
        group.sortPreferences();

        final int groupSize = group.getPreferenceCount();
        for (int i = 0; i < groupSize; i++) {
            final Preference preference = group.getPreference(i);
            
            // 遍歷PreferenceScreen持有的Preference列表添加到Adapter的列表中去。
            preferences.add(preference);
            
            // mHasReturnedViewTypeCount:
            // 初始值爲false,getItemViewType()調用過後置爲true。
            // 表示:是否已經返回了item的viewtype和count。

            // Preference#isRecycleEnabled:
            // 默認爲true,可由setIsRecycleEnabled()或Preference_recycleEnabled的style指定。
            // 表示:是否允許ListView緩存item的view。
            if (!mHasReturnedViewTypeCount && preference.isRecycleEnabled()) {
                // 既沒有返回viewtype且支持itemview的緩存的場合,取得Preference的layout信息並創建Layout實例後緩存到列表中。
                addPreferenceClassName(preference);
            }
            
            // 元素爲Group的場合,遞歸調用本方法繼續收集Preference信息。
            if (preference instanceof PreferenceGroup) {
                final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;

                // isOnSameScreenAsChildren:用於表示Group是否需要和其子Preference顯示在同一頁面,默認爲true。
                if (preferenceAsGroup.isOnSameScreenAsChildren()) {
                    flattenPreferenceGroup(preferences, preferenceAsGroup);
                }
            }

            // 所有Preference對象都將變化回調設置爲Adapter本身
            preference.setOnPreferenceChangeInternalListener(this);
        }
    }
    …
}

PreferenceGroupAdapter#addPreferenceClassName()

public class PreferenceGroupAdapter extends BaseAdapter… {
    private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
        // 創建PreferenceLayout實例
        PreferenceLayout pl = in != null? in : new PreferenceLayout();

        // 獲取Preference類信息,佈局ID及插件佈局ID並填充到Layout實例。
        pl.name = preference.getClass().getName();
        pl.resId = preference.getLayoutResource();
        pl.widgetResId = preference.getWidgetLayoutResource();
        return pl;
    }

    private void addPreferenceClassName(Preference preference) {
        // 創建每個Preference專屬的PreferenceLayout實例。
        final PreferenceLayout pl = createPreferenceLayout(preference, null);
        // 從Layout列表中查找該Layout實例,獲取待插入的下標。
        int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);

        // Layout列表中上不存在該Layout實例的話將該Layout添加到List中。
        if (insertPos < 0) {
            // Convert to insert index
            insertPos = insertPos * -1 - 1;
            mPreferenceLayouts.add(insertPos, pl);
        }
    }

    private static class PreferenceLayout implements Comparable<PreferenceLayout> {
        private int resId; // 持有classname等Preference的基本信息。
        …
        // 實現了比較器接口,按照名稱,佈局ID,插件佈局ID的順序去對比。
        // 以達到在List中排序查找的目的。
        public int compareTo(PreferenceLayout other) {
            int compareNames = name.compareTo(other.name);
            if (compareNames == 0) {
                if (resId == other.resId) {
                    if (widgetResId == other.widgetResId) {
                        return 0;
                    } else {
                        return widgetResId - other.widgetResId;
                    }
                } else {
                    return resId - other.resId;
                }
            } else {
                return compareNames;
            }
        }
    }
    …
}

 syncMyPreferences()裏創建完Preference列表和PreferenceLayout列表後將調用Adapter#notifyDataSetChanged()觸發ListView開始佈局,並將回調getView()。

PreferenceGroupAdapter#getView()

public class PreferenceGroupAdapter extends BaseAdapter… {
    public View getView(int position, View convertView, ViewGroup parent) {
        // 針對當前Preference實例做成臨時Layout實例。
        final Preference preference = this.getItem(position);
        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);

        // Layout列表中查找上述臨時實例
        // 不存在的話將已緩存View置null,告訴Preference需要創建
        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 ||
                (getItemViewType(position) == getHighlightItemViewType())) {
            convertView = null;
        }

        // Preference單獨準備各自的View視圖。
        View result = preference.getView(convertView, parent);
        if (position == mHighlightedPosition && mHighlightedDrawable != null) {
            // 當前下標需要高亮表示的話給當前View外層包裹一層高亮背景
            ViewGroup wrapper = new FrameLayout(parent.getContext());
            wrapper.setLayoutParams(sWrapperLayoutParams);
            wrapper.setBackgroundDrawable(mHighlightedDrawable);
            wrapper.addView(result);
            result = wrapper;
        }
        return result;
    }

    // 覆寫了getItem()從Preference列表中取得指定下標的Preference實例。
    public Preference getItem(int position) {
        if (position < 0 || position >= getCount()) return null;
        return mPreferenceList.get(position);
    }
    …
}

Preference#getView()

public class Preference implements Comparable<Preference> {
    …
    public View getView(View convertView, ViewGroup parent) {
        // 判斷View是否已緩存
        if (convertView == null) {
            convertView = onCreateView(parent); // 未緩存則新建
        }
        // 將View和數據綁定
        onBindView(convertView);
        return convertView;
    }

    protected View onCreateView(ViewGroup parent) {
        final LayoutInflater layoutInflater =
                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // 將Preference單獨的Layout解析並轉化爲View
        final View layout = layoutInflater.inflate(mLayoutResId, parent, false);

        // 需要顯示Widget的話將其佈局添加到上述View,否則將Widget隱藏
        final ViewGroup widgetFrame = (ViewGroup) layout
                .findViewById(com.android.internal.R.id.widget_frame);
        if (widgetFrame != null) {
            if (mWidgetLayoutResId != 0) {
                layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
            } else {
                widgetFrame.setVisibility(View.GONE);
            }
        }
        return layout;
    }

    // 將title,summary,icon等信息反映到對應View控件
    protected void onBindView(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);
                if (mHasSingleLineTitleAttr) {
                    titleView.setSingleLine(mSingleLineTitle);
                }
            } else {
                titleView.setVisibility(View.GONE);
            }
        }

        final TextView summaryView = (TextView) view.findViewById(
                com.android.internal.R.id.summary);
        if (summaryView != null) {
            final CharSequence summary = getSummary();
            if (!TextUtils.isEmpty(summary)) {
                summaryView.setText(summary);
                summaryView.setVisibility(View.VISIBLE);
            } else {
                summaryView.setVisibility(View.GONE);
            }
        }

        final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
        if (imageView != null) {
            if (mIconResId != 0 || mIcon != null) {
                if (mIcon == null) {
                    mIcon = getContext().getDrawable(mIconResId);
                }
                if (mIcon != null) {
                    imageView.setImageDrawable(mIcon);
                }
            }
            if (mIcon != null) {
                imageView.setVisibility(View.VISIBLE);
            } else {
                imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
            }
        }
        ...
    }
}

 

總結一下PreferenceGroupAdapter的上述邏輯。
1. 從PreferenceScreen中讀取並作成Preference列表和PreferenceLayout列表。
2. 手動觸發ListView的繪製處理,讓每個Preference單獨準備自己的itemView。
3. Preference的layout存在變化的可能,每次準備itemView之前首先從PreferenceLayout列表中檢查是否存在,如果不存在的話,告知Preference先執行View視圖的創建處理然後再綁定。

 

至此,APP調用PreferenceActivity#addPreferencesFromResource()後系統如何展示設置畫面的邏輯闡述完了。

簡化流程


STEP ① 解析Preference佈局文件得到PreferenceScreen
PreferenceActivity#addPreferencesFromResource()
PreferenceManager#inflateFromResource(xml id)
PreferenceInflater#inflate()
   PreferenceGroup#addPreference()

STEP ② 根據PreferenceScreen做成Adapter並和ListView綁定
PreferenceActivity#setPreferencesScreen()→bindPreferences()
PreferenceScreen#bind()
  PreferenceGroupAdapter#syncMyPreferences()

STEP ③ ListView繪製調用Preference準備itemView
PreferenceGroupAdapter#getView()
Preference#getView()→onCreateView()→onBindView()

除了PreferenceActivity我們知道還可以使用PreferenceFragment展示設置頁面。PreferenceFragment也提供了addPreferencesFromResource()供APP調用,使用方法幾乎一致。那麼原理也一樣嗎?

PreferenceFragment#addPreferencesFromResource()

public abstract class PreferenceFragment extends Fragment … {
    …
    public PreferenceManager getPreferenceManager() {
        return mPreferenceManager;
    }

   public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
            onUnbindPreferences();
            mHavePrefs = true;
            if (mInitDone) {
                postBindPreferences();
            }
        }
    }

    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
        requirePreferenceManager();
        setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
                preferencesResId, getPreferenceScreen()));
    }

    private void bindPreferences() {
        final PreferenceScreen preferenceScreen = getPreferenceScreen();
        if (preferenceScreen != null) {
            View root = getView();
            if (root != null) {
                View titleView = root.findViewById(android.R.id.title);
                if (titleView instanceof TextView) {
                    CharSequence title = preferenceScreen.getTitle();
                    if (TextUtils.isEmpty(title)) {
                        titleView.setVisibility(View.GONE);
                    } else {
                        ((TextView) titleView).setText(title);
                        titleView.setVisibility(View.VISIBLE);
                    }
                }
            }

            preferenceScreen.bind(getListView());
        }
        onBindPreferences();
    }
    …
}

 上述邏輯和PreferenceActivity的調用邏輯幾乎一樣,也是持有PreferenceManager實例去加載xml後得到PreferenceScreen對象後和ListView進行綁定。

但是有一點是有區別的,PreferenceActivity是繼承自ListActivity,通過加載包含ListView的佈局讓父類的關於ListView的API起效。

而PreferenceFragment是普通的Fragment組件,其ListView對象是自己單獨加載進來的,關於ListView實例的一些API也從ListActivity中拷貝了進來。

PreferenceFragment#onCreateView()

public abstract class PreferenceFragment extends Fragment … {
    private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;

    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        …
        // 從style中取得自定義佈局,默認爲包含ListView的preference_list_fragment.xml
        mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout,
                mLayoutResId);
        a.recycle();
        return inflater.inflate(mLayoutResId, container, false);
    }
    
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        …
        ListView lv = (ListView) view.findViewById(android.R.id.list);
        if (lv != null
                && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) {
            lv.setDivider(
                    a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider));
        }

        a.recycle();
    }
} 

上述原理可以看出,Android通過一系列抽象出來的Preference的組件和內部指定的ListView進行交互,讓APP簡單配置下Preference佈局,並不用和View打交道就可以快速搭建設置界面。

下一篇我們將研究下Android的常用Preference組件如何實現的。以及APP如何自定義Preference組件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章