您的位置:首页 > 移动开发 > Android开发

Android Settings 设置中 PreferenceActivity中,Header是如何被加载的?

2014-06-05 08:41 501 查看
-----------------------------------------------------

本文主要回答下面这个问题:

Android中,Header是如何被PreferenceActivity进行加载的?

-----------------------------------------------------

在Android应用程序中,我们可以在继承自PreferenceActivity的页面中通过两种方式,加载设置项。

其一:loadHeadersFromResource

[java] view
plaincopy





@Override

public void onBuildHeaders(List<header> headers) {

loadHeadersFromResource(R.xml.settings_headers, headers);

updateHeaderList(headers);

mHeaders = headers;

}</header>

settings_headers.xml

[html] view
plaincopy





<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>

其二:

[java] view
plaincopy





addPreferencesFromResource(R.xml.device_info_settings);

device_info_settings.xml

[html] view
plaincopy





<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

[java] view
plaincopy





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。即:

[java] view
plaincopy





parser = getResources().getXml(resid);

AttributeSet attrs = Xml.asAttributeSet(parser);

阶段二:进行xml文件解析,解析出来Headers。解析方式为Pull解析,这也是说android内部是使用Pull解析的。(关于Pull解析请移步:/article/2738745.html

[java] view
plaincopy





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异常。

[java] view
plaincopy





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进行赋值。

[java] view
plaincopy





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加入到列表中。

[java] view
plaincopy





target.add(header);

这里的target,参见上文,target为:PreferenceActivity中的mHeaders。

有人可能有疑问:为什么要在PreferenceActivity的继承类的onBuildHeaders方法中进行xml加载,onBuildHeaders什么时候调用?

理解了这个问题,也就明白了为什么上文中的target为PreferenceActivity中的mHeaders了。

答案就是,在PreferenceActivity的onCreate方法中,会调用onBuildHeader方法,同时将成员变量mHeader作为参数。

[java] view
plaincopy





@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的子类中:

[java] view
plaincopy





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中,会执行如下代码:

[java] view
plaincopy





setListAdapter(new HeaderAdapter(this, mHeaders));

PreferenceActivity继承自ListActivity,内部拥有ListView的引用。

上段代码就是我们常用的Adapter,ListView形式,不需要再介绍了吧。

附:HeaderAdapter的源码:

[java] view
plaincopy





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的整个流程。不当之处,欢迎大家提出宝贵意见,共同学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: