博客参考链接:Android5.0 Settings各个子模块跳转和布局实现
android Settings 添加新菜单项参考链接:Settings源码6.0 7.0添加新菜单选项
Settings继承自SettingsActivity,该类除了实现的空内部类外,就是一个判断Fragment是否有效的方法。实现的空内部类作用:在加载类设置项时不适用SubSettings,当创建快捷方式时调用queryIntentActivities()方法查询到的类为这些类的空实现的内部类。SettingsActivity的onCreate()方法如下:
private static final String META_DATA_KEY_FRAGMENT_CLASS = "com.android.settings.FRAGMENT_CLASS";
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// Should happen before any call to getIntent()
//1.获得meta-data中key为""com.android.settings.FRAGMENT_CLASS"的值,并赋值给MFragmentClass
getMetaData();
//2.
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
//3获取preference
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
Context.MODE_PRIVATE);
//获取Fragment的类名
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
//intent是否为快捷方式的intent
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
//获得intent中的组件名
final ComponentName cn = intent.getComponent();
//根据组件名获得类名
final String className = cn.getClassName();
//4 由于是从Settings这个类跳过来的,因此mIsShowingDashboard为true
mIsShowingDashboard = className.equals(Settings.class.getName());
//是否为SubSettings的判断逻辑;此时为false
final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
// If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
if (isSubSettings) {
// Check also that we are not a Theme Dialog as we don't want to override them
final int themeResId = getThemeResId();
if (themeResId != R.style.Theme_DialogWhenLarge &&
themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
setTheme(R.style.Theme_SubSettings);
}
}
//此时,由于mIsShowingDashboard 为true,因而加载的是settings_main_dashboard布局
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = (ViewGroup) findViewById(R.id.main_content);
getFragmentManager().addOnBackStackChangedListener(this);
if (mIsShowingDashboard) {
Index.getInstance(getApplicationContext()).update();
}
if (savedState != null) {
// We are restarting from a previous saved state; used that to initialize, instead
// of starting fresh.
mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
setTitleFromIntent(intent);
ArrayList<DashboardCategory> categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
if (categories != null) {
mCategories.clear();
mCategories.addAll(categories);
setTitleFromBackStack();
}
mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
1 /* one home activity by default */);
} else {
if (!mIsShowingDashboard) {
// Search is shown we are launched thru a Settings "shortcut". UP will be shown
// only if it is a sub settings
if (mIsShortcut) {
mDisplayHomeAsUpEnabled = isSubSettings;
mDisplaySearch = false;
} else if (isSubSettings) {
mDisplayHomeAsUpEnabled = true;
mDisplaySearch = true;
} else {
mDisplayHomeAsUpEnabled = false;
mDisplaySearch = false;
}
if (isActivityShouldBeAvoided(this.getClass().getSimpleName())) {
mDisplaySearch = false;
}
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);
} else {//首次加载时会进入该分支
// No UP affordance if we are displaying the main Dashboard
mDisplayHomeAsUpEnabled = false;
// Show Search affordance
mDisplaySearch = true;
mInitialTitleResId = R.string.dashboard_title;
//5 主要是切换到DashboardSummary这个Fragment
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
}
}
mActionBar = getActionBar();
if (mActionBar != null) {
mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
}
mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
// see if we should show Back/Next buttons
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
View buttonBar = findViewById(R.id.button_bar);
if (buttonBar != null) {
buttonBar.setVisibility(View.VISIBLE);
Button backButton = (Button)findViewById(R.id.back_button);
backButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED, getResultIntentData());
finish();
}
});
Button skipButton = (Button)findViewById(R.id.skip_button);
skipButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK, getResultIntentData());
finish();
}
});
mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK, getResultIntentData());
finish();
}
});
// set our various button parameters
if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
if (TextUtils.isEmpty(buttonText)) {
mNextButton.setVisibility(View.GONE);
}
else {
mNextButton.setText(buttonText);
}
}
if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
if (TextUtils.isEmpty(buttonText)) {
backButton.setVisibility(View.GONE);
}
else {
backButton.setText(buttonText);
}
}
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
skipButton.setVisibility(View.VISIBLE);
}
}
}
mHomeActivitiesCount = getHomeActivitiesCount();
}
1. private void getMetaData() {
try {
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
PackageManager.GET_META_DATA);
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
} catch (NameNotFoundException nnfe) {
// No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
getMetaData()函数用来获得Activity的额外数据mFragmentClass,如果可以获得这个数据,下面就会去现实mFragmentClass对应的Activity;而直接启动Settings模块是不会获得该数据的。那么,Activity时如何加载Fragment的呢?
2 @Override
public Intent getIntent() {
Intent superIntent = super.getIntent();
// 处理个别特殊情况,本例就是将mFragmentClass赋值给了startingFragment
String startingFragment = getStartingFragmentClass(superIntent);
// This is called from super.onCreate, isMultiPane() is not yet reliable
// Do not use onIsHidingHeaders either, which relies itself on this method
if (startingFragment != null) {
Intent modIntent = new Intent(superIntent);
// 将startingFragment放入intent的以EXTRA_SHOW_FRAGMENT(":settings:show_fragment")为key的键值对中。
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
Bundle args = superIntent.getExtras();
if (args != null) {
args = new Bundle(args);
} else {
args = new Bundle();
}
args.putParcelable("intent", superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
return modIntent;
}
return superIntent;
}
private String getStartingFragmentClass(Intent intent) {
if (mFragmentClass != null) return mFragmentClass;
String intentClass = intent.getComponent().getClassName();
if (intentClass.equals(getClass().getName())) return null;
if ("com.android.settings.ManageApplications".equals(intentClass)
|| "com.android.settings.RunningServices".equals(intentClass)
|| "com.android.settings.applications.StorageUse".equals(intentClass)) {
// Old names of manage apps.
intentClass = com.android.settings.applications.ManageApplications.class.getName();
}
return intentClass;
}
geIntent()函数的作用就是构造Intent,并且为它增加一个特殊的键值对,该键值对的key为"settings:show_fragment",value为mFragmentClass指定的Fragment类名。从这就可以看出,onCreate()方法要求必须先执行getMetaDate()方法,再去执行getIntent(),就是因为mFragmentClass是在getMetaData()方法中获得的。
5 DashboardSummary:是用来显示Settings所有项的,即点击Settings图标所展示的;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
mLayoutInflater = inflater;
mLte4GEnabler = new Lte4GEnabler(getActivity(), new Switch(getActivity()));
//加载了dashboard这个布局
final View rootView = inflater.inflate(R.layout.dashboard, container, false);
mDashboard = (ViewGroup) rootView.findViewById(R.id.dashboard_container);
return rootView;
}
从dashboard布局中可知,settings的选项视图是显示在dashboard_container上
@Override
public void onResume() {
super.onResume();
mLte4GEnabler.resume();
sendRebuildUI();//建立UI,就是发送一个MSG_REBUILD_UI消息,进而调用到RebuildUI方法
//包的安装、卸载等监听
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
getActivity().registerReceiver(mHomePackageReceiver, filter);
//飞行、SIM卡状态的广播
// Register for intent broadcasts
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
getActivity().registerReceiver(mReceiver, intentFilter);
}
private void rebuildUI(Context context) {
if (!isAdded()) {
Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");
return;
}
long start = System.currentTimeMillis();
final Resources res = getResources();
//先移除settings所有的视图
mDashboard.removeAllViews();
//获取Settings的所有categories,加载settings的所有内容
List<DashboardCategory> categories = ((SettingsActivity) context).getDashboardCategories(true);
//获取categories的总数
final int count = categories.size();
for (int n = 0; n < count; n++) {
//获取categories中的dashboardcategory
DashboardCategory category = categories.get(n);
//加载dashboard_category布局,mDashboard是整个界面的总View
View categoryView = mLayoutInflater.inflate(R.layout.dashboard_category, mDashboard,false);
TextView categoryLabel = (TextView) categoryView.findViewById(R.id.category_title);
categoryLabel.setText(category.getTitle(res));
ViewGroup categoryContent = (ViewGroup) categoryView.findViewById(R.id.category_content);
//获取dashboard所具有的dashboardTiles
final int tilesCount = category.getTilesCount();
for (int i = 0; i < tilesCount; i++) {
DashboardTile tile = category.getTile(i);
DashboardTileView tileView;
if (tile.getTitle(res).equals(res.getString(R.string.lte_4g_settings_title))) {
tileView = new DashboardTileView(context,true);
mLte4GEnabler.setSwitch(tileView.getSwitch());
int simState = TelephonyManager.getDefault().getSimState(PhoneConstants.SUB1);
boolean enabled = (Settings.System.getInt(context.getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) == 0)
&& (simState == TelephonyManager.SIM_STATE_READY);
tileView.setEnabled(enabled);
tileView.getTitleTextView().setEnabled(enabled);
// update icons
if (enabled) {
tile.iconRes = R.drawable.ic_settings_4g;
} else {
tile.iconRes = R.drawable.ic_settings_4g_dis;
updateTileView(context, res, tile, tileView.getImageView(),
tileView.getTitleTextView(), tileView.getSwitch());
} else {
tileView = new DashboardTileView(context,false);
updateTileView(context, res, tile, tileView.getImageView(),
tileView.getTitleTextView(), tileView.getStatusTextView());
}
tileView.setTile(tile);
categoryContent.addView(tileView);
}
// Add the category
mDashboard.addView(categoryView);
}
long delta = System.currentTimeMillis() - start;
Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
}
上面函数的作用:遍历categories这个列表来获取DashboardCategory对象,将所有的DashboardCategory对象和DashboardCAtegory对象中的DashboardTitle对象转换成视图对象并添加到主视图对象mDashboard中。
DashboardTitleViewz这个类是每一条目数据的类,通过onClcik()方法来启动不同的功能:如点击wifi或蓝牙等启动额界面。
明白了上述加载选项的方式,我们再次回到SettingsActivity中看如下两个函数就很清晰了:
public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
if (forceRefresh || mCategories.size() == 0) {
buildDashboardCategories(mCategories);
}
return mCategories;
}
/**
* Called when the activity needs its list of categories/tiles built.
*
* @param categories The list in which to place the tiles categories.
*/
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();
//加载了dashboard_categories布局
loadCategoriesFromResource(R.xml.dashboard_categories, categories);
updateTilesList(categories);
}
dashboard_categories布局就是加载所有的categories,这里的categories为ArrayList的mCategories;通过查看该布局,可知dashboard布局就是Settings模块的首界面的一个抽象,dashboard_categories则是设置分类集合的抽象。代码中的List对应的是dashboard_categories,DAshboardCategory对应的是dashboard_category,而dashboard_title对应的是DashboardTitlle。当加载完这些对象后SettingsActivity会得到mCategories返回给DashboardSummary来初始化Settings的各个设置选项。
介绍了Settings模块的加载,在此再简单介绍下点击时是如何跳转到各个功能界面的,这里以Storage选项简单举例:
从前面的分析可知:配置文件中的dashboard_tile对应的是DashboardTile,
DashboardTile又对应的是DashboardTileView视图,一个DashboardTileView对象就是设置模块中的一个设置选项;当我们点击Storage选项时,就会调用DashboardTileView的onClick()方法:
@Override
public void onClick(View v) {
if (mTile.fragment != null) {
Utils.startWithFragment(getContext(), mTile.fragment,mTile.fragmentArguments, null, 0,
mTile.titleRes, mTile.getTitle(getResources()));
} else if (mTile.intent != null) {
getContext().startActivity(mTile.intent);
}
}
mTile.fragment变量就代表的是dashboard_categories.xml文件中的
<!-- Storage -->
<dashboard-tile
android:id="@+id/storage_settings"
android:title="@string/storage_settings"
android:fragment="com.android.settings.deviceinfo.StorageSettings"
android:icon="@drawable/ic_settings_storage"
/>
Storage节点的
android:fragment="com.android.settings.deviceinfo.StorageSettings" 属性,因此就实现了跳转到各个功能选项的Fragment中。