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组件。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMerger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值