Android 4.4 Settings 应用初步分析

本文深入解析了Android系统中Settings应用的UI布局与交互原理,详细介绍了settings_headers.xml文件的作用及HeaderAdapter的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

一次偶然要在设置里面增加一个菜单,需要修改到settings_headers.xml 文件(res/layout/xml) 文件,所以就觉得要看一下这个流程.就做一下笔记,语言组织能力不行啊.
分析Android 源码的时候导入单个应用的时候一般是会有很多错误的,因为需要导入系统编译之后生成的jar包才能消除eclipse 里面的哪些红色xx.

1.Settings的UI

2.流程分析

从AndroidManifest.xml 中查看 <category android:name="android.intent.category.LAUNCHER" />
知道Settings.java 是这个应用入口activity.
Settings 继承了PreferenceActivity .他的布局文件是settings_headers.xml
这个文件里面都是这些header,效果可以参考上面的效果图1和图2.

<!-- WIRELESS and NETWORKS  分类-->
<header android:id="@+id/wireless_section"
	android:title="@string/header_category_wireless_networks" />

<!-- Sim management 普通项-->
<header
	android:id="@+id/sim_settings"
	android:icon="@drawable/ic_settings_dualsim"
	android:fragment="com.mediatek.gemini.SimManagement"
	android:title="@string/gemini_sim_management_title" />

com.android.settings.Settings.java 这个activity 是通过回调onBuildHeaders方法来加载进入应用之后的第一个布局文件的,然后调用 loadHeadersFromResource(R.xml.settings_headers, headers)来解析 文件.
onBuildHeaders 和loadHeadersFromResource 方法都是父类PreferenceActivity 的方法.
Settings.java 重写onBuildHeaders 方法的实现的源码如下:

/**
 * Populate the activity with the top-level headers.
 */
@Override
public void onBuildHeaders(List<Header> headers) {
	if (!onIsHidingHeaders()) {
		PDebug.Start("loadHeadersFromResource");
		loadHeadersFromResource(R.xml.settings_headers, headers);
		PDebug.End("loadHeadersFromResource");
		updateHeaderList(headers);
	}
}

loadHeadersFromResource 方法就是解析settings_headers.xml 文件并保持相关的数据到List<Header> headers 里面.
Header 定义很多变量来和settings_headers.xml 里面节点一一对应,

public long id = HEADER_ID_UNDEFINED;
public int titleRes;
public CharSequence title;
public String fragment;
public Bundle fragmentArguments;
public Intent intent;
public Bundle extras;
………

通过跟踪Setting.java 的父类(PreferenceActivity)的继承关系知道他其实也是一个ListActivity.java ,全部的设置项也是使用ListView来显示的.
HeaderAdapter这个适配类是Setting.java 的内部类,它会判断之后来加载对应的view和数据来显示UI.
HeaderAdapter已经定义了4中类型的View 类型

static final int HEADER_TYPE_CATEGORY = 0;//用来分类的
static final int HEADER_TYPE_NORMAL = 1;//常规项
static final int HEADER_TYPE_SWITCH = 2;//开关项
static final int HEADER_TYPE_BUTTON = 3;//按钮项

前3种应该都见过,为了让大家看到第4项,我把稍微修改了一下我的HeaderAdapter源码(见getview方法的中的有//add的部分),也就是上面图2中的security 选项.
HeaderAdapter 的getHeaderType 方法决定了配置在settings_headers.xml 里面的header的类型.
HeaderAdapter 的getView 方法根据header的类型 来加载对应的布局文件.

static int getHeaderType(Header header) {
	if (header.fragment == null && header.intent == null) {
		return HEADER_TYPE_CATEGORY;
	} else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings
			 || header.id == R.id.hotknot_settings) {
		return HEADER_TYPE_SWITCH;
	} else if (header.id == R.id.security_settings) {
		return HEADER_TYPE_BUTTON;
	} else {
		return HEADER_TYPE_NORMAL;
	}
}

但要注意的是在getView方法里面,当发现一个header 的类型是button的时候也会给header 的button增加一个onclick事件的,这个事件和header本事的onHeaderClick 是没有冲突的,因为2者不受同一个控件.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	HeaderViewHolder holder;
	Header header = getItem(position);
	int headerType = getHeaderType(header);
	Log.d("zhangle","getHeaderType" + header.title  + " headerType=" + headerType);
	View view = null;

	if (convertView == null) {
		holder = new HeaderViewHolder();
		switch (headerType) {
			case HEADER_TYPE_CATEGORY:
				view = new TextView(getContext(), null,
						android.R.attr.listSeparatorTextViewStyle);
				holder.title = (TextView) view;
				break;

			case HEADER_TYPE_SWITCH:
				view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
						false);
				holder.icon = (ImageView) view.findViewById(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);
				holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
				break;

			case HEADER_TYPE_BUTTON:
				view = mInflater.inflate(R.layout.preference_header_button_item, parent,
						false);
				holder.icon = (ImageView) view.findViewById(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);
				holder.button_ = (ImageButton) view.findViewById(R.id.buttonWidget);
				holder.divider_ = view.findViewById(R.id.divider);
				break;

			case HEADER_TYPE_NORMAL:
				view = mInflater.inflate(
						R.layout.preference_header_item, parent,
						false);
				holder.icon = (ImageView) view.findViewById(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);
				break;
		}
		view.setTag(holder);
	} else {
		view = convertView;
		holder = (HeaderViewHolder) view.getTag();
	}

	// All view fields must be updated every time, because the view may be recycled
	switch (headerType) {
		case HEADER_TYPE_CATEGORY:
			holder.title.setText(header.getTitle(getContext().getResources()));
			break;

		case HEADER_TYPE_SWITCH:
			// Would need a different treatment if the main menu had more switches
			if (header.id == R.id.wifi_settings) {
				mWifiEnabler.setSwitch(holder.switch_);
			} else if (header.id == R.id.bluetooth_settings){
				mBluetoothEnabler.setSwitch(holder.switch_);
			} else if (header.id == R.id.hotknot_settings){
				mHotKnotEnabler.setSwitch(holder.switch_);
			}
			updateCommonHeaderView(header, holder);
			break;

		case HEADER_TYPE_BUTTON:
			if (header.id == R.id.security_settings) {
				boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
				hasCert = true;//add
				if (hasCert) {
					holder.button_.setVisibility(View.VISIBLE);
					holder.divider_.setVisibility(View.VISIBLE);
					boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
					isManaged = true; //add
					if (isManaged) {
						holder.button_.setImageResource(R.drawable.ic_settings_about);
					} else {
						holder.button_.setImageResource(
								android.R.drawable.stat_notify_error);
					}
					holder.button_.setOnClickListener(new OnClickListener() {
						@Override
						public void onClick(View v) {
							Intent intent = new Intent(
									android.provider.Settings.ACTION_MONITORING_CERT_INFO);
							getContext().startActivity(intent);
						}
					});
				} else {
					holder.button_.setVisibility(View.GONE);
					holder.divider_.setVisibility(View.GONE);
				}
			}
			updateCommonHeaderView(header, holder);
			break;

		case HEADER_TYPE_NORMAL:
			updateCommonHeaderView(header, holder);
			break;
	}
	// /M: add for sim management feature
	if (header.id == R.id.sim_settings) {
		/// M: Customize SIM string
		holder.title.setText(mExt.customizeSimDisplayString(
			getContext().getString(R.string.gemini_sim_management_title), SLOT_ALL));
		handleDisableHolder(holder, view);
	} else {
		handleEnableHolder(holder, view);
	}
	return view;
}

那么每一个header 是如果响应点击操作的呢.这个就要看Setting.java的onHeaderClick 方法了, onHeaderClick 方法会调用父类的onHeaderClick方法来打开相关的应用,其父类是根据我们配置在settings_headers.xml里面的fragment和intent 来打开相对应的activity的.

Setting.java -- onHeaderClick

public void onHeaderClick(Header header, int position) {
        boolean revert = false;
        if (header.id == R.id.account_add) {
            revert = true;
        }

        super.onHeaderClick(header, position);

        if (revert && mLastHeader != null) {
            highlightHeader((int) mLastHeader.id);
        } else {
            mLastHeader = header;
        }
}

PreferenceActivity -- onHeaderClick

public void onHeaderClick(Header header, int position) {
	if (header.fragment != null) {
		if (mSinglePane) {
			Log.d(TAG, "onHeaderClick, single pane and startWithFragment.");
			int titleRes = header.breadCrumbTitleRes;
			int shortTitleRes = header.breadCrumbShortTitleRes;
			if (titleRes == 0) {
				titleRes = header.titleRes;
				shortTitleRes = 0;
			}
			startWithFragment(header.fragment, header.fragmentArguments, null, 0,
					titleRes, shortTitleRes);
		} else {
			Log.d(TAG, "onHeaderClick, multiple pane and switchToHeader.");
			switchToHeader(header);
		}
	} else if (header.intent != null) {
		Log.d(TAG, "onHeaderClick, start activity with header intent.");
		startActivity(header.intent);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值