最近在研究Android的Settings源码,先看一下源码的目录结构。大概967左右个文件,是不是及其头疼而且无从下手?待我娓娓道来~~~~~
1,初识Settings
首先,这么多文件,到底哪个文件是主界面呢?在Settings目录下找到Androidmanifest.xml清单配置文件,找到首先启动的activity:
- <activity android:name="Settings" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:launchMode="singleTask">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <action android:name="android.settings.SETTINGS" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
可以看到,设置的主界面是Settings.Java(package com.android.settings;),
- public class Settings extends PreferenceActivity
- implements ButtonBarHandler, OnAccountsUpdateListener {
- .....
- <pre name="code" class="java"> loadHeadersFromResource(R.xml.settings_headers, headers);
-
- .....
- }
所对应的xml文件为Settings_headers.xml(res\xml\)文件。在此摘列出xml文件的一部分。
- <preference-headers
- xmlns:android="http://schemas.android.com/apk/res/android">
-
-
- <!-- WIRELESS and NETWORKS -->
- <header android:id="@+id/wireless_section"
- 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" />
-
- <!-- MobileData -->
- <header
- android:id="@+id/mobiledata_settings"
- android:icon="@drawable/stat_notify_mobile_data"
- android:title="@string/data_usage_enable_mobile">
- <intent
- android:action="android.intent.action.MAIN"
- android:targetPackage="com.android.phone"
- android:targetClass="com.android.phone.MobileNetworkSettings" />
- </header>
- .........
- </preference-headers>
每个可以选择和点击的item基本有四个属性,以WiFi_header为例
id:对应的id
fragment:点击之后的fragment:WifiSettings
title:header的主标题,即在Settings主界面显示的文本:WLAN
icon:header的图标,即显示在文本左侧的图标
分析这两个文件可以总结下Settings的布局,Settings主界面显示借助PreferenceActivity,Preference意为偏爱偏好,特点是利用键值对记录用户上次的选择,在下次进入到该界面时直接读取上次的选择无须再进行配置。Activity意为界面,preferenceactivity结合两者。每行属于一个header,相当于listview中的item,每一个header又有fragment与之对应,而fragment的加载依赖于Activity,所依赖的Activity为SubSettings.java(package com.android.settings;//继承与Settings),在Subsetting.java中已经写明:
-
-
-
-
-
- public class SubSettings extends Settings {
-
- @Override
- public boolean onNavigateUp() {
- finish();
- return true;
- }
-
- @Override
- protected boolean isValidFragment(String fragmentName) {
- return true;
- }
- }
对Setting源码的分析可以分两个步骤进行入手,
第一,headers列表的加载
第二,header的点击事件的处理
解决以上两个问题后,就可以开始对不同模块进行分析
2,设置界面布局,加载headers
(1),加载xml布局文件
可以使用两种方式加载xml文件布局
方法一:
- loadHeadersFromResource(R.xml.settings_headers, headers);
方法二:
- addPreferencesFromResource(R.xml.fragmented_preferences_inner);
(2),定义adapter加载并显示headers
- private static class HeaderAdapter extends ArrayAdapter<Header> {
设置界面布局的适配器adapter,有以下几种type
i>,HEADER_TYPE_CATEGORY:无焦点,不可以点击
ii>,HEADER_TYPE_BUTTON:带有button的header,button的visibility(可见性)有条件(可自行设置)
iii>,HEADER_TYPE_NORMAL:正常的可获取焦点可点击的不带button的header
3,Settings.java源码分析(部分提取)
(1),onCreate方法中:
- if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
- getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
- }
以上这段代码用于布局actionbar,即顶部的导航栏布局,如果获取到的intent中的数值为
ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW,即表示,当屏幕较窄时导航栏有一部分会显示在底部。
- mAuthenticatorHelper = new AuthenticatorHelper();
- mAuthenticatorHelper.updateAuthDescriptions(this);
- mAuthenticatorHelper.onAccountsUpdated(this, null);
这段代码属于配置一些认证或者更新账户信息,一般不做修改
查看方法源码可以看到:方法是获取到配置文件Androidmanifest.xml中<meta-data.../>节点下的数据
- private void getMetaData() {
- try {
-
- ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
- PackageManager.GET_META_DATA);
-
- if (ai == null || ai.metaData == null) return;
-
- mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
-
- mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
-
-
-
-
- final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
-
- String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
-
- if (parentFragmentClass != null) {
- mParentHeader = new Header();
- mParentHeader.fragment = parentFragmentClass;
- if (parentHeaderTitleRes != 0) {
- mParentHeader.title = getResources().getString(parentHeaderTitleRes);
- }
- }
- } catch (NameNotFoundException nnfe) {
-
- }
- }
- if (!onIsHidingHeaders() && onIsMultiPane()) {
- highlightHeader(mTopLevelHeaderId);
-
-
- setTitle(R.string.settings_label);
- }
onIsMultiPane()判断是否双屏幕MultiPane,平板双屏显示,手机一般单屏SinglePane显示,所以onIsMultiPane()方法可以设置为返回false。
onIsHidingHeaders判断是否是双屏的headers均有显示。
如果满足条件就利用highlightHeader()方法标亮所选择的header进行区别于其他headers,并且将导航栏title定为设置,保证不被覆盖。
- if (onIsMultiPane()) {
-
- getActionBar().setDisplayHomeAsUpEnabled(false);
-
- getActionBar().setHomeButtonEnabled(false);
-
- getActionBar().setDisplayShowHomeEnabled(true)
- }
以上代码是说如果是多屏显示,则对导航栏左上角程序图标以及返回图标的设置
接下来是利用savedInstanceState恢复数据的操作,不再贴出
- showBreadCrumbs(mCurrentHeader.title, null);
设置当前header的标题显示
- if (mParentHeader != null) {
- setParentTitle(mParentHeader.title, null, new OnClickListener() {
- @Override
- public void onClick(View v) {
- 。。。。。。
- }
- });
- }
设置parentheader的标题title以及设置title的点击事件。
(2),onresume方法,显示出来所有的header,借助于headerAdapter.resume()方法显示
header即item需要显示什么类型的布局可以在该adapter中进行修改,针对不同的item配置不同的布局文件
- private static class HeaderAdapter extends ArrayAdapter<Header> {
- static int getHeaderType(Header header) {
- .........
- }
- public View getView(int position, View convertView, ViewGroup parent) {
- ..........
- }
- }
(3),onBuildStartFragmentIntent方法
- @Override
- public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
- int titleRes, int shortTitleRes) {
- Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
- titleRes, shortTitleRes);
-
-
-
-
-
- if (WifiSettings.class.getName().equals(fragmentName) ||
- WifiP2pSettings.class.getName().equals(fragmentName) ||
- BluetoothSettings.class.getName().equals(fragmentName) ||....) {
-
- intent.putExtra(EXTRA_UI_OPTIONS, ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
-
- }
-
- intent.setClass(this, SubSettings.class);
- return intent;
- }
(4),onBuildHeaders方法,用来布局,以及更新headers,在PreferenceActivity的oncreate()方法中被调用,以及onGetInitialHeader()方法,也是在PreferenceActivity的oncreate方法中被调用
- @Override
- public void onBuildHeaders(List<Header> headers) {
-
- if (!onIsHidingHeaders()) {
- loadHeadersFromResource(R.xml.settings_headers, headers);
-
-
-
- updateHeaderList(headers); } }
(5)doValidCheck(),以及isValidFragment 用来检查fragment是否有效,为适配Android4.4以下版本,保证不出异常
(6)onNewIntent:activity启动模式为singletask单任务模式,如果在战中存在activity的实例,当再次通过intent调起时不会再去oncreate创建实例,而是onNewIntent去重用该实例
- @Override
- public void onNewIntent(Intent intent) {
-
- super.onNewIntent(intent);
-
-
- if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
- if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
- switchToHeaderLocal(mFirstHeader);
- }
- getListView().setSelectionFromTop(0, 0);
- }
- }
(7)Settings.java中的内部类,Settings.java中有好多实现的内部类
- 。。。。。。
- public static class SecuritySettingsActivity extends Settings { }
- public static class LocationSettingsActivity extends Settings { }
- 。。。。。。。。
这些内部类是为了加载那些fragment,作为fragment的宿主,可以从Androidmanifest.xml中看到,从其他快捷方式进入某个单独的设置模块时借助这些内部类来加载。比如可以创建蓝牙快捷方式,以及状态栏进入蓝牙时需要借助这些内部类来加载那些fragment。
- <activity android:name="Settings$WirelessSettingsActivity"
- android:taskAffinity="com.android.settings"
- android:label="@string/wireless_networks_settings_title"
- android:parentActivityName="Settings">
- 。。。。。。
- </activity>
4,自定义操作
明白Settings界面的布局原理后我们就可以随意的对Settings主界面的布局进行增删改了,对应的是header的修改
(1),修改header:在xml文件下找到想要修改的header对应的节点,文本,文本左侧图标,以及点击进入的fragment进行相应修改即可
(2),增加header:例如我要增加一项"权限管理",做法如下:
i>,在Settings.headers.xml文件中增加一个header节点:
- <header
- android:id="@+id/authority_management
- android:fragment="com.android.settings.AuthorityManagementSettings"
- android:icon="@drawable/ic_settings_authority"
- android:title="@string/authority_settings"/>
ii>,新建一个fragment,AuthorityManagementSettings类
- public class DeviceInfoSettings extends RestrictedSettingsFragment {
- ........
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- addPreferencesFromResource(R.xml.authority_management_settings);
- .........
- }
- }