-----------------------------------------------------
本文主要回答下面这个问题:
Android中,Header是如何被PreferenceActivity进行加载的?
-----------------------------------------------------
在Android应用程序中,我们可以在继承自PreferenceActivity的页面中通过两种方式,加载设置项。
其一:loadHeadersFromResource
- @Override
- public void onBuildHeaders(List<header> headers) {
- loadHeadersFromResource(R.xml.settings_headers, headers);
- updateHeaderList(headers);
- mHeaders = headers;
- }</header>
settings_headers.xml
- <preference-headers
- xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- WIRELESS and NETWORKS -->
- <header android:title="@string/header_category_wireless_networks" />
- <!-- Wifi -->
- <header
- android:id="@+id/wifi_settings"
- android:fragment="com.android.settings.wifi.WifiSettings"
- android:title="@string/wifi_settings_title"
- android:icon="@drawable/ic_settings_wireless" />
- <!-- Bluetooth -->
- <header
- android:id="@+id/bluetooth_settings"
- android:fragment="com.android.settings.bluetooth.BluetoothSettings"
- android:title="@string/bluetooth_settings_title"
- android:icon="@drawable/ic_settings_bluetooth2" />
- </preference-headers>
其二:
- addPreferencesFromResource(R.xml.device_info_settings);
device_info_settings.xml
- <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/about_settings">
- <!-- System update settings - launches activity -->
- <PreferenceScreen android:key="system_update_settings"
- android:title="@string/system_update_settings_list_item_title"
- android:summary="@string/system_update_settings_list_item_summary">
- <intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS" />
- </PreferenceScreen>
- <PreferenceScreen android:key="additional_system_update_settings"
- android:title="@string/additional_system_update_settings_list_item_title">
- <intent android:action="android.intent.action.MAIN"
- android:targetPackage="@string/additional_system_update"
- android:targetClass="@string/additional_system_update_menu" />
- </PreferenceScreen>
- </PreferenceScreen>
这两种方式都可以满足我们的需求,今天我们主要来看一下第一种方式。
使用Header,内部大体经历了两个阶段,第一、解析xml配置文件,构建Header列表。第二、构建adapter进行Header列表的加载以及显示。
1、在PreferenceActivity.java中,调用loadHeadersFromResource进行Header的加载。
流程如下:
PreferenceActivity.java
- public void loadHeadersFromResource(int resid, List<Header> target) {
- XmlResourceParser parser = null;
- try {
- parser = getResources().getXml(resid);
- AttributeSet attrs = Xml.asAttributeSet(parser);
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- // Parse next until start tag is found
- }
- String nodeName = parser.getName();
- if (!"preference-headers".equals(nodeName)) {
- throw new RuntimeException(
- "XML document must start with <preference-headers> tag; found"
- + nodeName + " at " + parser.getPositionDescription());
- }
- Bundle curBundle = null;
- final int outerDepth = parser.getDepth();
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- nodeName = parser.getName();
- if ("header".equals(nodeName)) {
- Header header = new Header();
- TypedArray sa = getResources().obtainAttributes(attrs,
- com.android.internal.R.styleable.PreferenceHeader);
- header.id = sa.getResourceId(
- com.android.internal.R.styleable.PreferenceHeader_id,
- (int)HEADER_ID_UNDEFINED);
- TypedValue tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_title);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.titleRes = tv.resourceId;
- } else {
- header.title = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_summary);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.summaryRes = tv.resourceId;
- } else {
- header.summary = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.breadCrumbTitleRes = tv.resourceId;
- } else {
- header.breadCrumbTitle = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.breadCrumbShortTitleRes = tv.resourceId;
- } else {
- header.breadCrumbShortTitle = tv.string;
- }
- }
- header.iconRes = sa.getResourceId(
- com.android.internal.R.styleable.PreferenceHeader_icon, 0);
- header.fragment = sa.getString(
- com.android.internal.R.styleable.PreferenceHeader_fragment);
- sa.recycle();
- if (curBundle == null) {
- curBundle = new Bundle();
- }
- final int innerDepth = parser.getDepth();
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String innerNodeName = parser.getName();
- if (innerNodeName.equals("extra")) {
- getResources().parseBundleExtra("extra", attrs, curBundle);
- XmlUtils.skipCurrentTag(parser);
- } else if (innerNodeName.equals("intent")) {
- header.intent = Intent.parseIntent(getResources(), parser, attrs);
- } else {
- XmlUtils.skipCurrentTag(parser);
- }
- }
- if (curBundle.size() > 0) {
- header.fragmentArguments = curBundle;
- curBundle = null;
- }
- target.add(header);
- } else {
- XmlUtils.skipCurrentTag(parser);
- }
- }
- } catch (XmlPullParserException e) {
- throw new RuntimeException("Error parsing headers", e);
- } catch (IOException e) {
- throw new RuntimeException("Error parsing headers", e);
- } finally {
- if (parser != null) parser.close();
- }
- }
上面的这段代码是基于android 4.0版本截取的,代码内容大体可以分为三个阶段。
- 阶段一:将resId对应的xml文件,加载成为XmlResourceParser、AttributeSet。即:
- parser = getResources().getXml(resid);
- AttributeSet attrs = Xml.asAttributeSet(parser);
- 阶段二:进行xml文件解析,解析出来Headers。解析方式为Pull解析,这也是说android内部是使用Pull解析的。(关于Pull解析请移步:http://blog.csdn.net/droyon/article/details/9346885)
- if (!"preference-headers".equals(nodeName)) {
- throw new RuntimeException(
- "XML document must start with <preference-headers> tag; found"
- + nodeName + " at " + parser.getPositionDescription());
- }
首先xml的标题头部必须为“preference-headers”,否则,抛出Runntime异常。
- if ("header".equals(nodeName)) {
- Header header = new Header();
- TypedArray sa = getResources().obtainAttributes(attrs,
- com.android.internal.R.styleable.PreferenceHeader);
- header.id = sa.getResourceId(
- com.android.internal.R.styleable.PreferenceHeader_id,
- (int)HEADER_ID_UNDEFINED);
- TypedValue tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_title);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.titleRes = tv.resourceId;
- } else {
- header.title = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_summary);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.summaryRes = tv.resourceId;
- } else {
- header.summary = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.breadCrumbTitleRes = tv.resourceId;
- } else {
- header.breadCrumbTitle = tv.string;
- }
- }
- tv = sa.peekValue(
- com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
- if (tv != null && tv.type == TypedValue.TYPE_STRING) {
- if (tv.resourceId != 0) {
- header.breadCrumbShortTitleRes = tv.resourceId;
- } else {
- header.breadCrumbShortTitle = tv.string;
- }
- }
- header.iconRes = sa.getResourceId(
- com.android.internal.R.styleable.PreferenceHeader_icon, 0);
- header.fragment = sa.getString(
- com.android.internal.R.styleable.PreferenceHeader_fragment);
- sa.recycle();
这部分解析出来Header对象,并且对Header对象的title,summary,以及id,frament进行赋值。
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String innerNodeName = parser.getName();
- if (innerNodeName.equals("extra")) {
- getResources().parseBundleExtra("extra", attrs, curBundle);
- XmlUtils.skipCurrentTag(parser);
- } else if (innerNodeName.equals("intent")) {
- header.intent = Intent.parseIntent(getResources(), parser, attrs);
- } else {
- XmlUtils.skipCurrentTag(parser);
- }
- }
这部分解析extra以及Intent。
- 阶段三、将解析出来的Header加入到列表中。
- target.add(header);
这里的target,参见上文,target为:PreferenceActivity中的mHeaders。
有人可能有疑问:为什么要在PreferenceActivity的继承类的onBuildHeaders方法中进行xml加载,onBuildHeaders什么时候调用?
理解了这个问题,也就明白了为什么上文中的target为PreferenceActivity中的mHeaders了。
答案就是,在PreferenceActivity的onCreate方法中,会调用onBuildHeader方法,同时将成员变量mHeader作为参数。
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(com.android.internal.R.layout.preference_list_content);
- //........
- // We need to try to build <span style="color:#ff0000;"><strong>the headers.</strong>
- </span> <span style="color:#ff0000;"><strong>onBuildHeaders(mHeaders);</strong>
- </span>
- // If there are headers, then at this point we need to show
- // them and, depending on the screen, we may also show in-line
- // the currently selected preference fragment.
- if (mHeaders.size() > 0) {
- if (!mSinglePane) {
- if (initialFragment == null) {
- Header h = onGetInitialHeader();
- switchToHeader(h);
- } else {
- switchToHeader(initialFragment, initialArguments);
- }
- }
- }
- }
- }
- //........
- }
我们在继承自PreferenceActivity的子类中:
- public class Settings extends PreferenceActivity{
- @Override
- public void onBuildHeaders(List<Header> headers) {
- loadHeadersFromResource(R.xml.settings_headers, headers);
- updateHeaderList(headers);
- mHeaders = headers;
- }
- }
回答完毕。
通过上面的介绍,我们完成了第一步,即:xml配置文件的解析以及Header的列表加载。
第二、adapter的构建。
通过第一步,PreferenceActivity得到了一个Header的List集合,mHeaders。
同样是在PreferenceActivity的onCreate中,会执行如下代码:
- setListAdapter(new HeaderAdapter(this, mHeaders));
PreferenceActivity继承自ListActivity,内部拥有ListView的引用。
上段代码就是我们常用的Adapter,ListView形式,不需要再介绍了吧。
附:HeaderAdapter的源码:
- private static class HeaderAdapter extends ArrayAdapter<Header> {
- private static class HeaderViewHolder {
- ImageView icon;
- TextView title;
- TextView summary;
- }
- private LayoutInflater mInflater;
- public HeaderAdapter(Context context, List<Header> objects) {
- super(context, 0, objects);
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- HeaderViewHolder holder;
- View view;
- if (convertView == null) {
- 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();
- }
- // 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;
- }
- }
以上就是PreferenceActivity加载Header的整个流程。不当之处,欢迎大家提出宝贵意见,共同学习。