下面是Settings应用的截图:
可以看出这是很典型的使用了Fragment后的界面,设置里面有WIFI、蓝牙、显示、存储、应用等众多功能。左边的每一项,对应着右边的一个设置界面, Fragment有四个子类: DialogFragment, ListFragment, PreferenceFragment, WebViewFragment。很明显,Settings用的是 PreferenceFragment。接着看一下Settings源码package结构:
主题部分的实现主要在com.android.settings下面,其他包主要是用于各自功能实现,所以重点说这个包下面的类。在AndroidManifest.xml文字中,看到程序入口是Settings类:
打开Settings类,是继承于PreferenceActivity:
其他的继承关系如下: PreferenceActivity --> ListActivity --> Activity。 PreferenceActivity主要用于Settings,关于如何使用可以参考API( http://developer.android.com/reference/android/preference/PreferenceActivity.html )以及guide( http://developer.android.com/guide/topics/ui/settings.html )。和它相关联的类有header、fragment、preference。每一个header就是左边的一个选项条目,像蓝牙、应用等,选择之后右边就会显示对应的fragment(平板),然后fragment和preference联系在一起,组成了一个个设置项。一般在activity中设置布局,用的是setContentView(),在 PreferenceActivity中,是需要继承 onBuildHeaders(List)这个方法,
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
去生成选项表,点击选项表的一个条目,右边显示对应的Fragment,这就是很典型的header+fragment组合,所以如果想在Settings基础之上添加条目的话,在这个方法里面的xml文件中添加即可,然后对应上fragment。下面分析一下执行流程:
首先进入 onCreate(Bundle savedInstanceState)方法里面,代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) {
getWindow().setUiOptions(0);
}
mAuthenticatorHelper = new AuthenticatorHelper();
mAuthenticatorHelper.updateAuthDescriptions(this);
mAuthenticatorHelper.onAccountsUpdated(this, null);
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
Context.MODE_PRIVATE);
getMetaData();
mInLocalHeaderSwitch = true;
super.onCreate(savedInstanceState);
mInLocalHeaderSwitch = false;
if (!onIsHidingHeaders() && onIsMultiPane()) {
highlightHeader(mTopLevelHeaderId);
// Force the title so that it doesn't get overridden by a direct launch of
// a specific settings screen.
setTitle(R.string.settings_label);
}
// Retrieve any saved state
if (savedInstanceState != null) {
mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);
}
// If the current header was saved, switch to it
if (savedInstanceState != null && mCurrentHeader != null) {
//switchToHeaderLocal(mCurrentHeader);
showBreadCrumbs(mCurrentHeader.title, null);
}
if (mParentHeader != null) {
setParentTitle(mParentHeader.title, null, new OnClickListener() {
public void onClick(View v) {
switchToParent(mParentHeader.fragment);
}
});
}
// Override up navigation for multi-pane, since we handle it in the fragment breadcrumbs
if (onIsMultiPane()) {
getActionBar().setDisplayHomeAsUpEnabled(false);
getActionBar().setHomeButtonEnabled(false);
}
}
第一个if用于设置window ui的对修改来说不用考虑了,意义不大,
mAuthenticatorHelper = new AuthenticatorHelper(); mAuthenticatorHelper.updateAuthDescriptions(this); mAuthenticatorHelper.onAccountsUpdated(this, null);这个段代码用于认证以及更新账户信息,接着往下看:
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,Context.MODE_PRIVATE);
用于之后保存数据, 然后是getMetaData();这个方法,代码如下:
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);
// Check if it has a parent specified and create a Header object
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) {
// No recovery
}
}
这个方法用于设置mParentHeader的Fragment以及title。下面具体举两个例子,关于如何修改Settings。
header即是左边的菜单,如下图左侧。它的布局文件在res下的xml文件夹中,名字是settings_headers.xml。打开如下:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" > <header android:id="@+id/wireless_section" android:title="@string/header_category_wireless_networks" /> <header android:id="@+id/wifi_settings" android:fragment="com.android.settings.wifi.WifiSettings" android:icon="@drawable/ic_settings_wireless" android:title="@string/wifi_settings_title" /> <header android:id="@+id/bluetooth_settings" android:fragment="com.android.settings.bluetooth.BluetoothSettings" android:icon="@drawable/ic_settings_bluetooth2" android:title="@string/bluetooth_settings_title" /> <header android:id="@+id/data_usage_settings" android:fragment="com.android.settings.DataUsageSummary" android:icon="@drawable/ic_settings_data_usage" android:title="@string/data_usage_summary_title" /> <header android:id="@+id/operator_settings" android:fragment="com.android.settings.WirelessSettings" > <intent android:action="com.android.settings.OPERATOR_APPLICATION_SETTING" /> header> <header android:id="@+id/wireless_settings" android:breadCrumbTitle="@string/wireless_networks_settings_title" android:fragment="com.android.settings.WirelessSettings" android:icon="@drawable/empty_icon" android:title="@string/radio_controls_title" /> <header android:id="@+id/device_section" android:title="@string/header_category_device" /> <header android:id="@+id/sound_settings" android:fragment="com.android.settings.SoundSettings" android:icon="@drawable/ic_settings_sound" android:title="@string/sound_settings" /> <header android:id="@+id/display_settings" android:fragment="com.android.settings.DisplaySettings" android:icon="@drawable/ic_settings_display" android:title="@string/display_settings" /> <header android:id="@+id/storage_settings" android:fragment="com.android.settings.deviceinfo.Memory" android:icon="@drawable/ic_settings_storage" android:title="@string/storage_settings" /> <header android:id="@+id/battery_settings" android:fragment="com.android.settings.fuelgauge.PowerUsageSummary" android:icon="@drawable/ic_settings_battery" android:title="@string/power_usage_summary_title" /> <header android:id="@+id/application_settings" android:fragment="com.android.settings.applications.ManageApplications" android:icon="@drawable/ic_settings_applications" android:title="@string/applications_settings" /> <header android:id="@+id/user_settings" android:fragment="com.android.settings.users.UserSettings" android:icon="@drawable/ic_settings_multiuser" android:title="@string/user_settings_title" /> <header android:id="@+id/manufacturer_settings" android:fragment="com.android.settings.WirelessSettings" > <intent android:action="com.android.settings.MANUFACTURER_APPLICATION_SETTING" /> header> <header android:id="@+id/personal_section" android:title="@string/header_category_personal" /> <header android:id="@+id/location_settings" android:fragment="com.android.settings.LocationSettings" android:icon="@drawable/ic_settings_location" android:title="@string/location_settings_title" /> <header android:id="@+id/security_settings" android:fragment="com.android.settings.SecuritySettings" android:icon="@drawable/ic_settings_security" android:title="@string/security_settings_title" /> <header android:id="@+id/language_settings" android:fragment="com.android.settings.inputmethod.InputMethodAndLanguageSettings" android:icon="@drawable/ic_settings_language" android:title="@string/language_settings" /> <header android:id="@+id/privacy_settings" android:fragment="com.android.settings.PrivacySettings" android:icon="@drawable/ic_settings_backup" android:title="@string/privacy_settings" /> <header android:id="@+id/kytusers_settings" android:fragment="com.android.settings.KytUsersSettings" android:icon="@drawable/ic_settings_backup" android:title="考易通账户" /> <header android:id="@+id/account_settings" android:title="@string/account_settings" /> <header android:id="@+id/account_add" android:icon="@drawable/ic_menu_add" android:title="@string/add_account_label" > <intent android:action="android.settings.ADD_ACCOUNT_SETTINGS" /> header> <header android:id="@+id/system_section" android:title="@string/header_category_system" /> <header android:id="@+id/date_time_settings" android:fragment="com.android.settings.DateTimeSettings" android:icon="@drawable/ic_settings_date_time" android:title="@string/date_and_time_settings_title" /> <header android:id="@+id/accessibility_settings" android:fragment="com.android.settings.AccessibilitySettings" android:icon="@drawable/ic_settings_accessibility" android:title="@string/accessibility_settings" /> <header android:id="@+id/development_settings" android:fragment="com.android.settings.DevelopmentSettings" android:icon="@drawable/ic_settings_development" android:title="@string/development_settings_title" /> <header android:id="@+id/about_settings" android:fragment="com.android.settings.DeviceInfoSettings" android:icon="@drawable/ic_settings_about" android:title="@string/about_settings" /> preference-headers>这些header分别对应着各自的菜单,如果想要添加还是删除就在这里修改即可。比如我们不想要蓝牙模块了,那就直接把下面这个header删除即可,添加的话类似。
<header android:id="@+id/bluetooth_settings" android:fragment="com.android.settings.bluetooth.BluetoothSettings" android:icon="@drawable/ic_settings_bluetooth2" android:title="@string/bluetooth_settings_title" />如果是做添加操作的话,不要忘了创建你的PreferenceFragment,然后在header里面添加id、fragment、icon、title等,如上面那样。
二、修改显示的应用
先看一下应用显示的部分:
看一下在源码中对应的包:
应用显示是一个滑动的界面,猜测是用ViewPager实现的,下面开始寻找实现。首先进入xml文件夹找到settings_headers,找到这一段代码:
<header android:id="@+id/application_settings" android:fragment="com.android.settings.applications.ManageApplications" android:icon="@drawable/ic_settings_applications" android:title="@string/applications_settings" />然后打开对应的fragment,ManageApplications。发现这个它是继承于Fragment
既然继承于Fragment,那就直接定位到onCreate()方法,
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); Intent intent = getActivity().getIntent(); String action = intent.getAction(); int defaultListType = LIST_TYPE_DOWNLOADED; String className = getArguments() != null ? getArguments().getString("classname") : null; if (className == null) { className = intent.getComponent().getClassName(); } if (className.equals(RunningServicesActivity.class.getName()) || className.endsWith(".RunningServices")) { defaultListType = LIST_TYPE_RUNNING; } else if (className.equals(StorageUseActivity.class.getName()) || Intent.ACTION_MANAGE_PACKAGE_STORAGE.equals(action) || className.endsWith(".StorageUse")) { mSortOrder = SORT_ORDER_SIZE; defaultListType = LIST_TYPE_ALL; } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) { // Select the all-apps list, with the default sorting defaultListType = LIST_TYPE_ALL; } if (savedInstanceState != null) { mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder); int tmp = savedInstanceState.getInt(EXTRA_DEFAULT_LIST_TYPE, -1); if (tmp != -1) defaultListType = tmp; mShowBackground = savedInstanceState.getBoolean(EXTRA_SHOW_BACKGROUND, false); } mDefaultListType = defaultListType; final Intent containerIntent = new Intent().setComponent( StorageMeasurement.DEFAULT_CONTAINER_COMPONENT); getActivity().bindService(containerIntent, mContainerConnection, Context.BIND_AUTO_CREATE); mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); mComputingSizeStr = getActivity().getText(R.string.computing_size); TabInfo tab = new TabInfo(this, mApplicationsState, getActivity().getString(R.string.filter_apps_third_party), LIST_TYPE_DOWNLOADED, this, savedInstanceState); mTabs.add(tab); if (!Environment.isExternalStorageEmulated()) { tab = new TabInfo(this, mApplicationsState, getActivity().getString(R.string.filter_apps_onsdcard), LIST_TYPE_SDCARD, this, savedInstanceState); mTabs.add(tab); } tab = new TabInfo(this, mApplicationsState, getActivity().getString(R.string.filter_apps_running), LIST_TYPE_RUNNING, this, savedInstanceState); mTabs.add(tab); tab = new TabInfo(this, mApplicationsState, getActivity().getString(R.string.filter_apps_all), LIST_TYPE_ALL, this, savedInstanceState); mTabs.add(tab); }这里主要是初始化TabInfo的数据,之后显示程序的时候会用到。接下来定位到onCreateView()方法,这个方法主要是初始化界面,
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// initialize the inflater
mInflater = inflater;
View rootView = mInflater.inflate(R.layout.manage_applications_content,
container, false);
mContentContainer = container;
mRootView = rootView;
mViewPager = (ViewPager) rootView.findViewById(R.id.pager);
MyPagerAdapter adapter = new MyPagerAdapter();
mViewPager.setAdapter(adapter);
mViewPager.setOnPageChangeListener(adapter);
PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light);
// We have to do this now because PreferenceFrameLayout looks at it
// only when the view is added.
if (container instanceof PreferenceFrameLayout) {
((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true;
}
if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_RESET_DIALOG)) {
buildResetDialog();
}
if (savedInstanceState == null) {
// First time init: make sure view pager is showing the correct tab.
for (int i = 0; i < mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.mListType == mDefaultListType) {
mViewPager.setCurrentItem(i);
break;
}
}
}
return rootView;
}
可以看到,正是使用了ViewPager,另外还有PagerTabStrip。先看一下最下面的:
if (savedInstanceState == null) { // First time init: make sure view pager is showing the correct tab. for (int i = 0; i < mTabs.size(); i++) { TabInfo tab = mTabs.get(i); if (tab.mListType == mDefaultListType) { mViewPager.setCurrentItem(i); break; } } }这段代码的作用就是用于设置默认显示哪个选项卡的程序,所以就是“已下载”这个界面。然后回过头看一下上面的代码,
MyPagerAdapter adapter = new MyPagerAdapter();
mViewPager.setAdapter(adapter);
这个就是熟悉的适配器了,所以显示程序的数据在这里面。开打这个类,
class MyPagerAdapter extends PagerAdapter
implements ViewPager.OnPageChangeListener {
int mCurPos = 0;
@Override
public int getCount() {
return mTabs.size();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
TabInfo tab = mTabs.get(position);
View root = tab.build(mInflater, mContentContainer, mRootView);
container.addView(root);
return root;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public CharSequence getPageTitle(int position) {
return mTabs.get(position).mLabel;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurPos = position;
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
updateCurrentTab(mCurPos);
}
}
}
可以看到,有几个滑动的页卡,是通过mTabs这个类来控制的,而它就是 TabInfo类型。接着看一下绘制视图的方法:
MyPagerAdapter @Override public Object instantiateItem(ViewGroup container, int position) { TabInfo tab = mTabs.get(position); View root = tab.build(mInflater, mContentContainer, mRootView); container.addView(root); return root; }首先是获取到一个tab,然后这个tab用它的build()方法去生成一个视图,最后放到ViewPager显示。所以问题的重点分析就是TabInfo这个类了。先说buid()方法,
public View build(LayoutInflater inflater, ViewGroup contentParent, View contentChild) { if (mRootView != null) { return mRootView; } mInflater = inflater; mRootView = inflater .inflate( mListType == LIST_TYPE_RUNNING ? R.layout.manage_applications_running : R.layout.manage_applications_apps, null); mLoadingContainer = mRootView.findViewById(R.id.loading_container); mLoadingContainer.setVisibility(View.VISIBLE); mListContainer = mRootView.findViewById(R.id.list_container); if (mListContainer != null) { // Create adapter and list view here View emptyView = mListContainer .findViewById(com.android.internal.R.id.empty); ListView lv = (ListView) mListContainer .findViewById(android.R.id.list); if (emptyView != null) { lv.setEmptyView(emptyView); } lv.setOnItemClickListener(this); lv.setSaveEnabled(true); lv.setItemsCanFocus(true); lv.setTextFilterEnabled(true); mListView = lv; mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter); mListView.setAdapter(mApplications); mListView.setRecyclerListener(mApplications); mColorBar = (LinearColorBar) mListContainer .findViewById(R.id.storage_color_bar); mStorageChartLabel = (TextView) mListContainer .findViewById(R.id.storageChartLabel); mUsedStorageText = (TextView) mListContainer .findViewById(R.id.usedStorageText); mFreeStorageText = (TextView) mListContainer .findViewById(R.id.freeStorageText); Utils.prepareCustomPreferencesList(contentParent, contentChild, mListView, false); if (mFilter == FILTER_APPS_SDCARD) { mStorageChartLabel.setText(mOwner.getActivity().getText( R.string.sd_card_storage)); } else { mStorageChartLabel.setText(mOwner.getActivity().getText( R.string.internal_storage)); } applyCurrentStorage(); } mRunningProcessesView = (RunningProcessesView) mRootView .findViewById(R.id.running_processes); if (mRunningProcessesView != null) { mRunningProcessesView.doCreate(mSavedInstanceState); } return mRootView; }第四行是生成了一view对象,
mRootView = inflater.inflate(mListType == LIST_TYPE_RUNNING ? R.layout.manage_applications_running: R.layout.manage_applications_apps, null);
这行代码用来判断加载哪个布局文件,确实viewpager虽然动态显示四个页面,但是其中三个的布局是一样的,唯一不一样的就是显示正在运行的界面。
ListView lv = (ListView) mListContainer
.findViewById(android.R.id.list);
if (emptyView != null) {
lv.setEmptyView(emptyView);
}
lv.setOnItemClickListener(this);
lv.setSaveEnabled(true);
lv.setItemsCanFocus(true);
lv.setTextFilterEnabled(true);
mListView = lv;
mApplications = new ApplicationsAdapter(mApplicationsState,
this, mFilter);
mListView.setAdapter(mApplications);
这段代码就是用于显示app程序了,然后定位到 ApplicationsAdapter这个类,在它的构造方法里面,传入三个参数。一个是 mApplicationsState对象,以后用于对 ApplicationsState类进行操作; 一个是TabInfo,用来显示不同的界面;一个是过滤器,是标识显示哪个界面。进入这个构造方法,
public ApplicationsAdapter(ApplicationsState state, TabInfo tab, int filterMode) { mState = state; mSession = state.newSession(this); mTab = tab; mContext = tab.mOwner.getActivity(); mFilterMode = filterMode; }发现没有在这里传入什么数据,然后看一下ApplicationsAdapter这个类,发现它继承了三个接口,第一个是过滤用的,第三个是系统SDK接口用于ListView循环处理。而第二个接口正是负责处理数据的,
它有六个回调方法:
public static interface Callbacks {
public void onRunningStateChanged(boolean running);
public void onPackageListChanged();
public void onRebuildComplete(ArrayList apps);
public void onPackageIconChanged();
public void onPackageSizeChanged(String packageName);
public void onAllSizesComputed();
}
第三个回调方法onRebuildComplete(ArrayList@Override
public void onResume() {
super.onResume();
mActivityResumed = true;
updateCurrentTab(mViewPager.getCurrentItem());
updateOptionsMenu();
}
首先是调用了updateCurrentTab(mViewPager.getCurrentItem())方法,然后updateCurrentTab方法里面又调用了TabInfo的resume方法,在 TabInfo的resume方法里面接着调用了 ApplicationsAdapter 的 resume方法,又在 ApplicationsAdapter 的 resume方法里面调用 Session的resume方法,最后又在 Session的resume方法里面调用 doResumeIfNeededLocked()方法,这个方法就是从系统读取程序信息的,代码如下:
void doResumeIfNeededLocked() { if (mResumed) { return; } mResumed = true; if (mPackageIntentReceiver == null) { mPackageIntentReceiver = new PackageIntentReceiver(); mPackageIntentReceiver.registerReceiver(); } //这个mApplications就是所有程序数据,如果你想过滤哪些程序的信息,对这个集合进行修改即可。比如你在做定制机的时候,不想自己的程序显示在Settings里面,那就在这里修改。 mApplications = mPm.getInstalledApplications(mRetrieveFlags); if (mApplications == null) { mApplications = new ArrayList至此,完成了显示程序的剖析,其他模块类似,只要按照流程走就行了。(); } if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { // If an interesting part of the configuration has changed, we // should completely reload the app entries. mEntriesMap.clear(); mAppEntries.clear(); } else { for (int i = 0; i < mAppEntries.size(); i++) { mAppEntries.get(i).sizeStale = true; } } for (int i = 0; i < mApplications.size(); i++) { final ApplicationInfo info = mApplications.get(i); // Need to trim out any applications that are disabled by // something different than the user. if (!info.enabled && info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { mApplications.remove(i); i--; continue; } final AppEntry entry = mEntriesMap.get(info.packageName); if (entry != null) { entry.info = info; } } mCurComputingSizePkg = null; if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { mBackgroundHandler .sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); } };
1、 Android PreferenceActivity点击Header是如何处理的?
2、 android-setting
3、 Android4.0设置界面修改总结
4、 Android源码之“应用程序界面“分析一( 从settings开始)
5、 Android 应用程序分析之Settings
6、 菜鸟Android4.0 Settings分析(一)
7、 Android Settings源码结构分析与自实现
8、Settings源码分析
9、Settings修改记录