转载请注明出处:http://blog.youkuaiyun.com/droyon/article/details/17798697
-----------------------------------------------------
本文主要回答下面这个问题:
Android中,Header是如何被PreferenceActivity进行加载的?
-----------------------------------------------------
在Android应用程序中,我们可以在继承自PreferenceActivity的页面中通过两种方式,加载设置项。
其一:loadHeadersFromResource
@Override
public void onBuildHeaders(List
headers) {
loadHeadersFromResource(R.xml.settings_headers, headers);
updateHeaderList(headers);
mHeaders = headers;
}
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.youkuaiyun.com/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 the headers.
onBuildHeaders(mHeaders);
// 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的整个流程。不当之处,欢迎大家提出宝贵意见,共同学习。