在阅读Android系统应用Settings源码的时候,我们会发现它的布局文件有一大批Preference控件,这个是什么东西呢?其实Preference是android原生为了持久化数据的一种"控件",注意它并不是真正意义上的View。
Preference家族代码被定义在frameworks/base/core/java/android/preference,改目录下的代码用来实现偏好设置相关的界面。即Preference是android原生为了持久化数据的一种"控件"。要使用Preference就必须建立在PreferenceActivity或PreferenceFragment的载体上面。具体用法可以参考《Android之PreferenceFragment详解》
Preference被定义在frameworks/base/core下面,其中Preference作为视图控件在整个家族中的地位类似于View,PreferenceGroup就类似于ViewGroup,XXXPreference则继承于Preference并扩展了不同功能样式。除此之外Preference并不能直接使用在Activity和Fragment上面,因此多了PreferenceActivity和PreferenceFragment他们分别继承Activity和Fragment并在此基础上实现了数据持久化、控件解析等功能。除此之外还有两个比较重要的类: PreferenceScreen和PreferenceGroupAdapter

Preference并不是控件,它没有继承View,但是为什么我们的布局文件中可以使用他呢?其实只有继承了PreferenceFragment或者PreferenceActivity的才能使用上面以PreferenceScreen标签开头的xml文件,因为他们内部作了一系列解析,同时Preference有方法返回一个View对象,如下代码:
- //android/frameworks/base/core/java/android/preference/Preference.java
- @Deprecated
- public class Preference implements Comparable
{ - private boolean mShouldDisableView = true;
- @UnsupportedAppUsage
- private int mLayoutResId = com.android.internal.R.layout.preference;
- @UnsupportedAppUsage
- private int mWidgetLayoutResId;
- public void setLayoutResource(@LayoutRes int layoutResId) {
- if (layoutResId != mLayoutResId) mRecycleEnabled = false;
- mLayoutResId = layoutResId;
- }
- @LayoutRes
- public int getLayoutResource() {
- return mLayoutResId;
- }
- //返回一个视图控件View
- public View getView(View convertView, ViewGroup parent) {
- //如果第一次就创建视图View
- if (convertView == null) convertView = onCreateView(parent);
- //给视图View填充数据和更新ui
- onBindView(convertView);
- return convertView;
- }
- //创建视图控件View,实际上还是inflate对应的资源ID文件
- @CallSuper
- 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) {
- if (mWidgetLayoutResId != 0) layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
- else widgetFrame.setVisibility(View.GONE);
- }
- return layout;
- }
- //主要是给上面创建的视图View设置数据
- @CallSuper
- 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);
- }
- final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame);
- if (imageFrame != null) {
- if (mIcon != null) imageFrame.setVisibility(View.VISIBLE);
- else imageFrame.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
- }
- if (mShouldDisableView) setEnabledStateOnViews(view, isEnabled());
- }
- //界面更改条目栏
- public void setTitle(CharSequence title) {
- if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
- mTitleRes = 0;
- mTitle = title;
- notifyChanged();
- }
- }
- //界面更改图标
- public void setIcon(Drawable icon) {
- if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) {
- mIcon = icon;
- notifyChanged();
- }
- }
- //界面有改变回调监听器
- protected void notifyChanged() {
- if (mListener != null) mListener.onPreferenceChange(this);
- }
- }
Settings为什么大量使用了Preference,而没有使用到我们常见的View和TextView呢,因为这里逻辑功能上主要是为了给系统进行一些设置,所有就涉及到了设置参数持久化(即断电后还继续生效),如下代码它内部已经通过PreferenceManager来进行对数据的持久化,实际上还是使用了四大存储方式之一
- //android/frameworks/base/core/java/android/preference/Preference.java
- @Deprecated
- public class Preference implements Comparable
{ - @Nullable
- private PreferenceManager mPreferenceManager;
- public SharedPreferences getSharedPreferences() {
- if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
- return mPreferenceManager.getSharedPreferences();
- }
- public SharedPreferences.Editor getEditor() {
- if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
- return mPreferenceManager.getEditor();
- }
- public boolean shouldCommit() {
- if (mPreferenceManager == null) return false;
- return mPreferenceManager.shouldCommit();
- }
- public PreferenceManager getPreferenceManager() {
- return mPreferenceManager;
- }
- }
第一章初步介绍了Preference的基本用法和一些特点,想使用Preference必须建立在PreferenceActivity或者PreferenceFragment的基础之上,除此之外布局文件最外层必须使用一个PreferenceScreen嵌套所有的Preference。
PreferenceActivity的布局跟主流布局类似,即通常由三部分组成:标题栏、内容、最底层的bar。这里我们关注标题栏和内容,因为PreferenceActivity在设计的时候就考虑到了公用同一个activity进行页面跳转,即替换内容区域的fragment。
- //frameworks/base/core/java/android/preference/PreferenceActivity.java
- /*继承ListActivity,即其布局文件中必须有一个ListView控件且id必须为list*/
- @Deprecated public abstract class PreferenceActivity extends ListActivity implements
- /*Preference树结构下所有视图控件点击回调事件*/
- PreferenceManager.OnPreferenceTreeClickListener,
- /*Preference被点击后如果有设置fragment就直接替换fragment回调*/
- PreferenceFragment.OnPreferenceStartFragmentCallback {
- //通过android:fragment属性进行跳转不同fragment页面,可能共用同一个PreferenceActivity,但是标题栏不一样,mHeaders 存储了所有的标题
- private final ArrayList
mHeaders = new ArrayList(); - private FrameLayout mListFooter;
- private ViewGroup mPrefsContainer;
- private CharSequence mActivityTitle;
- private ViewGroup mHeadersContainer;
- //当前对应的标题,Header表示一个标题
- private Header mCurHeader;
- //管理器
- private PreferenceManager mPreferenceManager;
- //数据持久化
- private Bundle mSavedInstanceState;
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //布局文件加载的是preference_list_content.xml
- final int layoutResId = sa.getResourceId(
- com.android.internal.R.styleable.PreferenceActivity_layout,
- com.android.internal.R.layout.preference_list_content);
- setContentView(layoutResId);
- mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
- mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
- mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers);
- //......
- }
- }
- //frameworks/base/core/res/res/layout/preference_list_content.xml
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <LinearLayout
- android:id="@+id/prefs_container"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="1">
-
- <LinearLayout
- style="?attr/preferenceHeaderPanelStyle"
- android:id="@+id/headers"
- android:orientation="vertical"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="@integer/preferences_left_pane_weight">
- <ListView android:id="@android:id/list"
- style="?attr/preferenceListStyle"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="1"
- android:clipToPadding="false"
- android:drawSelectorOnTop="false"
- android:cacheColorHint="@android:color/transparent"
- android:listPreferredItemHeight="48dp"
- android:scrollbarAlwaysDrawVerticalTrack="true" />
- <FrameLayout android:id="@+id/list_footer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="0" />
- LinearLayout>
-
- <LinearLayout
- android:id="@+id/prefs_frame"
- style="?attr/preferencePanelStyle"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="@integer/preferences_right_pane_weight"
- android:orientation="vertical">
- <include layout="@layout/breadcrumbs_in_fragment" />
- <android.preference.PreferenceFrameLayout android:id="@+id/prefs"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- />
- LinearLayout>
- LinearLayout>
-
- LinearLayout>
- //frameworks/base/core/java/android/preference/PreferenceActivity.java
- @Deprecated public abstract class PreferenceActivity extends ListActivity{
- //Header对应当前界面,即存储了当前界面的基本信息:
- // titleRes和title为标题栏
- // summaryRes和summary为概述
- @Deprecated public static final class Header implements Parcelable {
- @StringRes
- public int titleRes;
- public CharSequence title;
- @StringRes
- public int summaryRes;
- public CharSequence summary;
- //对应的ICON图标
- public int iconRes;
- //存储了对应的fragment的类名,点击Preference会自动跳转到android:fragment属性的值对应的类(通过反射进行实例化)
- public String fragment;
- //需要传递到fragment的参数
- public Bundle fragmentArguments;
- public Intent intent;
- public Bundle extras;
- //序列化上面的内容,包括fragment的类名
- @Override public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(id);
- dest.writeInt(titleRes);
- TextUtils.writeToParcel(title, dest, flags);
- dest.writeInt(summaryRes);
- TextUtils.writeToParcel(summary, dest, flags);
- dest.writeInt(breadCrumbTitleRes);
- TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
- dest.writeInt(breadCrumbShortTitleRes);
- TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
- dest.writeInt(iconRes);
- dest.writeString(fragment);
- dest.writeBundle(fragmentArguments);
- if (intent != null) {
- dest.writeInt(1);
- intent.writeToParcel(dest, flags);
- } else dest.writeInt(0);
- dest.writeBundle(extras);
- }
- //反序列化上面的内容,包括fragment的类名
- public void readFromParcel(Parcel in) {
- id = in.readLong();
- titleRes = in.readInt();
- title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- summaryRes = in.readInt();
- summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- breadCrumbTitleRes = in.readInt();
- breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- breadCrumbShortTitleRes = in.readInt();
- breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- iconRes = in.readInt();
- fragment = in.readString();
- fragmentArguments = in.readBundle();
- if (in.readInt() != 0) intent = Intent.CREATOR.createFromParcel(in);
- extras = in.readBundle();
- }
- Header(Parcel in) {
- readFromParcel(in);
- }
- public static final @android.annotation.NonNull Creator
CREATOR = new Creator() { - public Header createFromParcel(Parcel source) {
- return new Header(source);
- }
- public Header[] newArray(int size) {
- return new Header[size];
- }
- };
- }
- private static class HeaderAdapter extends ArrayAdapter
{ - private static class HeaderViewHolder {
- ImageView icon;
- TextView title;
- TextView summary;
- }
- private LayoutInflater mInflater;
- private int mLayoutResId;
- private boolean mRemoveIconIfEmpty;
- public HeaderAdapter(Context context, List
objects, int layoutResId, boolean removeIconBehavior) { - super(context, 0, objects);
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mLayoutResId = layoutResId;
- mRemoveIconIfEmpty = removeIconBehavior;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- HeaderViewHolder holder;
- View view;
- if (convertView == null) {
- view = mInflater.inflate(mLayoutResId, 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();
- }
- }
- }
- }
- //frameworks/base/core/java/android/preference/PreferenceActivity.java
- @Deprecated public abstract class PreferenceActivity extends ListActivity{
- //该方法很多地方被调用,例如创建或者点击需要切换fragment或者当前header的时候
- public void switchToHeader(Header header) {
- //判断是否当前header,是不切换
- if (mCurHeader == header) {
- getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- } else {
- if (header.fragment == null) throw new IllegalStateException("can't switch to header that has no fragment");
- //切换对应的fragment
- switchToHeaderInner(header.fragment, header.fragmentArguments);
- //切换header布局里面的字符串和图标等信息
- setSelectedHeader(header);
- }
- }
- //还是使用了FragmentTransaction 方式进行切换
- private void switchToHeaderInner(String fragmentName, Bundle args) {
- getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- if (!isValidFragment(fragmentName)) throw new IllegalArgumentException("Invalid fragment for this activity: " + fragmentName);
- Fragment f = Fragment.instantiate(this, fragmentName, args);
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.setTransition(mSinglePane
- ? FragmentTransaction.TRANSIT_NONE
- : FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- transaction.replace(com.android.internal.R.id.prefs, f);
- transaction.commitAllowingStateLoss();
- if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) {
- mPrefsContainer.setVisibility(View.VISIBLE);
- mHeadersContainer.setVisibility(View.GONE);
- }
- }
- //切换header的内容,通过触发点击事件
- void setSelectedHeader(Header header) {
- mCurHeader = header;
- int index = mHeaders.indexOf(header);
- if (index >= 0) getListView().setItemChecked(index, true);
- else getListView().clearChoices();
- showBreadCrumbs(header);
- }
- }
PreferenceFragment的布局跟PreferenceActivity类似,如下代码:
- //frameworks/base/core/java/android/preference/PreferenceFragment.java
- @Deprecated public abstract class PreferenceFragment extends Fragment implements
- /*Preference树结构下所有视图控件点击回调事件*/
- PreferenceManager.OnPreferenceTreeClickListener {
- @UnsupportedAppUsage
- private PreferenceManager mPreferenceManager;
- private ListView mList;
- //PreferenceFragment的布局文件,与PreferenceActivity使用了同样的布局
- private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
- private static final int MSG_BIND_PREFERENCES = 1;
- private Handler mHandler = new Handler() {
- @Override public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_BIND_PREFERENCES:
- bindPreferences();
- break;
- }
- }
- };
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
- mPreferenceManager.setFragment(this);
- }
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- //加载preference_list_fragment.xml,如果有acitivity沿用
- mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout, mLayoutResId);
- return inflater.inflate(mLayoutResId, container, false);
- }
- //从布局文件中找到list
- @Override 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));
- }
- }
- //这里很重要
- @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- //bindPreferences方法是用来绑定加载所有的Preference类的
- if (mHavePrefs) bindPreferences();
- mInitDone = true;
- if (savedInstanceState != null) {
- Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
- if (container != null) {
- //如果有缓存PreferenceScreen,直接获取出来
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- if (preferenceScreen != null) preferenceScreen.restoreHierarchyState(container);
- }
- }
- }
- //设置Preference点击事件
- @Override public void onStart() {
- super.onStart();
- mPreferenceManager.setOnPreferenceTreeClickListener(this);
- }
- @Override public void onStop() {
- super.onStop();
- mPreferenceManager.dispatchActivityStop();
- mPreferenceManager.setOnPreferenceTreeClickListener(null);
- }
- }
- public abstract class PreferenceFragment{
- private static final int MSG_BIND_PREFERENCES = 1;
- private Handler mHandler = new Handler() {
- @Override public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_BIND_PREFERENCES:
- bindPreferences();
- break;
- }
- }
- };
- private void bindPreferences() {
- //获取当前对应的PreferenceScreen
- 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());
- }
- //回调子类(继承于PreferenceFragment中的onBindPreferences方法,通常在子类该方法钟回调所有需要显示的Preference控件)
- onBindPreferences();
- }
- //通过mPreferenceManager获取当前的PreferenceScreen
- public PreferenceScreen getPreferenceScreen() {
- return mPreferenceManager.getPreferenceScreen();
- }
- //通过mPreferenceManager设置当前的PreferenceScreen
- public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
- if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
- onUnbindPreferences();
- mHavePrefs = true;
- if (mInitDone) postBindPreferences();
- }
- }
- //这个PreferenceScreen怎么来的呢,最终还是用户调用该接口设置进去的
- public void addPreferencesFromResource(@XmlRes int preferencesResId) {
- requirePreferenceManager();
- setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(), preferencesResId, getPreferenceScreen()));
- }
- }