为什么80%的码农都做不了架构师?>>>
笔者前段时间完成设置的圆角item风格的修改,但最近,客户新增需求,想把设置做成Tab风格的,没办法,顾客就是上帝,咱得改啊。今天算是初步改完了,趁着测试紧张测试的空隙,赶紧写写博客,梳理一下,一来是给自己记个笔记,二来希望给有需要的朋友一点帮助。
修改主要是两个点:
①.圆角item的实现。
②.Tab风格的实现。
先来看看下面粗糙的效果图:
我们都知道,Android从3.0开始加入了Fragment,这大大的减少了Activity的使用,在4.0的设置中尤其发挥的淋漓尽致,几乎整个设置应用的一级、二级设置菜单就属于一个Activity即Settings.java。我们能看到不同的设置项,其实就是更换不同的Fragment,表面上是显得很简洁明了,但是修改起来还是有点蛋疼了,一点小改动就有可能牵扯一大部分功能,引起一堆bug。
- 圆角item的实现:
其实这种圆角的item有两种类型,一种是一直都有的Preference,另外一种是从3.0开始添加的Header。比如设置的第一个主界面使用的就是Header(第一、二张图片所示),其二级、三级菜单就是Preference(第三张图片所示)了,不管是哪种类型,他们都是基于ListView实现的,理解了这个原理,修改起来就简单了,只用在ListView的Adapter的getView函数中,修改对应item项的背景图片即可。观察圆角item的背景图片我们可以发现,先要准备四种背景图片:顶部(上圆下方)、中间(上下都方)、底部(上方下圆)和单独(上下都圆)。
由于Header和Preference还是有一点区别的,Header使用频率没有Preference高,因此,我这里就没有在framework层修改Header,只是在需要用到的地方再修改。所以重点说一下Preference。
在源码中找到:frameworks/base/core/java/android/preference/PreferenceGroupAdapter.java,这个就是Preference的Adapter,首先声明4种类型的背景,然后通过计算得知那一项需要替换成对应的背景图片,最后在getView函数中,把对应的item的背景替换成我们需要的,下面是源码,加上liweiping标签的就是我的修改:
/** Copyright (C) 2007 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package android.preference;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;import android.os.Handler;
import android.preference.Preference.OnPreferenceChangeInternalListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.ListView;/*** An adapter that returns the {@link Preference} contained in this group.* In most cases, this adapter should be the base class for any custom* adapters from {@link Preference#getAdapter()}.* <p>* This adapter obeys the* {@link Preference}'s adapter rule (the* {@link Adapter#getView(int, View, ViewGroup)} should be used instead of* {@link Preference#getView(ViewGroup)} if a {@link Preference} has an* adapter via {@link Preference#getAdapter()}).* <p>* This adapter also propagates data change/invalidated notifications upward.* <p>* This adapter does not include this {@link PreferenceGroup} in the returned* adapter, use {@link PreferenceCategoryAdapter} instead.* * @see PreferenceCategoryAdapter*/
class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {private static final String TAG = "PreferenceGroupAdapter";/*** The group that we are providing data from.*/private PreferenceGroup mPreferenceGroup;/*** Maps a position into this adapter -> {@link Preference}. These* {@link Preference}s don't have to be direct children of this* {@link PreferenceGroup}, they can be grand children or younger)*/private List<Preference> mPreferenceList;/*** List of unique Preference and its subclasses' names. This is used to find* out how many types of views this adapter can return. Once the count is* returned, this cannot be modified (since the ListView only checks the* count once--when the adapter is being set). We will not recycle views for* Preference subclasses seen after the count has been returned.*/private ArrayList<PreferenceLayout> mPreferenceLayouts;private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();/*** Blocks the mPreferenceClassNames from being changed anymore.*/private boolean mHasReturnedViewTypeCount = false;private volatile boolean mIsSyncing = false;private Handler mHandler = new Handler(); //start by liweiping 20130826private ArrayList<Integer> mPreferenceListBackgroundIndex;private final int SINGLE_LINE_ROUND_CORNER_BACKGROUND = 1;private final int TOP_ROUND_CORNER_BACKGROUND = 2;private final int BOTTOM_ROUND_CORNER_BACKGROUND = 3;private final int CENTER_RECTANGLE_BACKGROUND = 4;private final int NO_BACKGOUND = 5;//end by liweiping 20130826private Runnable mSyncRunnable = new Runnable() {public void run() {syncMyPreferences();}};private static class PreferenceLayout implements Comparable<PreferenceLayout> {private int resId;private int widgetResId;private String name;public int compareTo(PreferenceLayout other) {int compareNames = name.compareTo(other.name);if (compareNames == 0) {if (resId == other.resId) {if (widgetResId == other.widgetResId) {return 0;} else {return widgetResId - other.widgetResId;}} else {return resId - other.resId;}} else {return compareNames;}}}public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {mPreferenceGroup = preferenceGroup;// If this group gets or loses any children, let us knowmPreferenceGroup.setOnPreferenceChangeInternalListener(this);mPreferenceList = new ArrayList<Preference>();mPreferenceLayouts = new ArrayList<PreferenceLayout>();syncMyPreferences();}private void syncMyPreferences() {synchronized(this) {if (mIsSyncing) {return;}mIsSyncing = true;}List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());mPreferenceListBackgroundIndex = new ArrayList<Integer>(mPreferenceList.size());//add by liweiping 20130826flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);mPreferenceList = newPreferenceList;notifyDataSetChanged();synchronized(this) {mIsSyncing = false;notifyAll();}}private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {// TODO: shouldn't always?group.sortPreferences();final int groupSize = group.getPreferenceCount();final int[] tempIndexOfPrefrence = calcItemsBetweenCategory(group);//add by liweiping 20130826for (int i = 0; i < groupSize; i++) {final Preference preference = group.getPreference(i);preferences.add(preference);//start by liweiping 20130826 if(tempIndexOfPrefrence[i] == 0){mPreferenceListBackgroundIndex.add(NO_BACKGOUND);} else if (tempIndexOfPrefrence[i] == 1&& (i == 0 ? true : tempIndexOfPrefrence[i - 1] <= 1)){if(i == (groupSize - 1) ? true : tempIndexOfPrefrence[i + 1] <= 1){mPreferenceListBackgroundIndex.add(SINGLE_LINE_ROUND_CORNER_BACKGROUND);}else {mPreferenceListBackgroundIndex.add(TOP_ROUND_CORNER_BACKGROUND);}}else if(tempIndexOfPrefrence[i] > 1){if(i == (groupSize - 1) ? true : tempIndexOfPrefrence[i + 1] <= 1){mPreferenceListBackgroundIndex.add(BOTTOM_ROUND_CORNER_BACKGROUND);}else {mPreferenceListBackgroundIndex.add(CENTER_RECTANGLE_BACKGROUND);}}//end by liweiping 20130826if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {addPreferenceClassName(preference);}if (preference instanceof PreferenceGroup) {final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;if (preferenceAsGroup.isOnSameScreenAsChildren()) {flattenPreferenceGroup(preferences, preferenceAsGroup);}}preference.setOnPreferenceChangeInternalListener(this);}}/*** Creates a string that includes the preference name, layout id and widget layout id.* If a particular preference type uses 2 different resources, they will be treated as* different view types.*/private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {PreferenceLayout pl = in != null? in : new PreferenceLayout();pl.name = preference.getClass().getName();pl.resId = preference.getLayoutResource();pl.widgetResId = preference.getWidgetLayoutResource();return pl;}private void addPreferenceClassName(Preference preference) {final PreferenceLayout pl = createPreferenceLayout(preference, null);int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);// Only insert if it doesn't exist (when it is negative).if (insertPos < 0) {// Convert to insert indexinsertPos = insertPos * -1 - 1;mPreferenceLayouts.add(insertPos, pl);}}public int getCount() {return mPreferenceList.size();}public Preference getItem(int position) {if (position < 0 || position >= getCount()) return null;return mPreferenceList.get(position);}public long getItemId(int position) {if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;return this.getItem(position).getId();}public View getView(int position, View convertView, ViewGroup parent) {final Preference preference = this.getItem(position);// Build a PreferenceLayout to compare with known ones that are cacheable.mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);// If it's not one of the cached ones, set the convertView to null so that // the layout gets re-created by the Preference.if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {convertView = null;}// start by liweiping 20130826 //return preference.getView(convertView, parent);View mView = preference.getView(convertView, parent);mView.setBackgroundResource(com.android.internal.R.color.transparent);if(mPreferenceListBackgroundIndex.get(position) == SINGLE_LINE_ROUND_CORNER_BACKGROUND){mView.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_single);}else if(mPreferenceListBackgroundIndex.get(position) == TOP_ROUND_CORNER_BACKGROUND){mView.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_top);}else if(mPreferenceListBackgroundIndex.get(position) == CENTER_RECTANGLE_BACKGROUND){mView.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_center);}else if(mPreferenceListBackgroundIndex.get(position) == BOTTOM_ROUND_CORNER_BACKGROUND){mView.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_bottom);}return mView;//end by liweiping 20130826}@Overridepublic boolean isEnabled(int position) {if (position < 0 || position >= getCount()) return true;return this.getItem(position).isSelectable();}@Overridepublic boolean areAllItemsEnabled() {// There should always be a preference group, and these groups are always// disabledreturn false;}public void onPreferenceChange(Preference preference) {notifyDataSetChanged();}public void onPreferenceHierarchyChange(Preference preference) {mHandler.removeCallbacks(mSyncRunnable);mHandler.post(mSyncRunnable);}@Overridepublic boolean hasStableIds() {return true;}@Overridepublic int getItemViewType(int position) {if (!mHasReturnedViewTypeCount) {mHasReturnedViewTypeCount = true;}final Preference preference = this.getItem(position);if (preference.hasSpecifiedLayout()) {return IGNORE_ITEM_VIEW_TYPE;}mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);if (viewType < 0) {// This is a class that was seen after we returned the count, so// don't recycle it.return IGNORE_ITEM_VIEW_TYPE;} else {return viewType;}}@Overridepublic int getViewTypeCount() {if (!mHasReturnedViewTypeCount) {mHasReturnedViewTypeCount = true;}return Math.max(1, mPreferenceLayouts.size());}//start by liweiping 20130826private int[] calcItemsBetweenCategory(PreferenceGroup group){final int groupSize = group.getPreferenceCount();int[] indexOfPreference = new int[groupSize];for (int i = 0; i < groupSize; i++) {final Preference preference = group.getPreference(i);if (preference instanceof PreferenceCategory) {indexOfPreference[i] = 0 ;}else if(i == 0){indexOfPreference[i] = 1 ;}else {indexOfPreference[i] = indexOfPreference[i - 1] + 1;}}return indexOfPreference;}//end by liweiping 20130826
}
- tab风格的实现
tab风格的实现有两种方法,一种是传统的TabHost,另外一种就是从3.0开始的ActionBar中的tab,我最开始使用的就是TabHost,也大概实现了,但是最后的Bug让我头疼死了,因此果断放弃,选择了ActionBar的tab,其实一开始就应该选择这个的,由于笔者过度自信对TabHost的使用熟练度,因此结果就是不得不返工,浪费时间精力。其实用ActionBar的tab对代码的改动应该是最小的。下面来看看我修改过后的Settings.java的源码(我的修改也是打过liweiping标签的):
/** Copyright (C) 2008 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings;import com.android.settings.accounts.AccountSyncSettings;
import com.android.settings.bluetooth.BluetoothEnabler;
import com.android.settings.fuelgauge.PowerUsageSummary;
import com.android.settings.wifi.WifiEnabler;
//import static com.sprd.android.config.OptConfig.LC_RAM_SUPPORT;import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
import android.app.FragmentTransaction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.TopwiseProp;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.MenuItem;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Switch;
import android.widget.TextView;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;/*** Top-level settings activity to handle single pane and double pane UI layout.*/
public class Settings extends PreferenceActivity implements ButtonBarHandler {private static final String LOG_TAG = "Settings";private static final String META_DATA_KEY_HEADER_ID ="com.android.settings.TOP_LEVEL_HEADER_ID";private static final String META_DATA_KEY_FRAGMENT_CLASS ="com.android.settings.FRAGMENT_CLASS";private static final String META_DATA_KEY_PARENT_TITLE ="com.android.settings.PARENT_FRAGMENT_TITLE";private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS ="com.android.settings.PARENT_FRAGMENT_CLASS";private static final String EXTRA_CLEAR_UI_OPTIONS = "settings:remove_ui_options";private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";private static final String SAVE_KEY_PARENT_HEADER = "com.android.settings.PARENT_HEADER";public static boolean UNIVERSEUI_SUPPORT = SystemProperties.getBoolean("universe_ui_support",false);private String mFragmentClass;private int mTopLevelHeaderId;private Header mFirstHeader;private Header mCurrentHeader;private Header mParentHeader;private boolean mInLocalHeaderSwitch;// TODO: Update Call Settings based on airplane mode state.protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();private List<Header> mHeaders;//start by liweiping 20140103private int mTabFlag;private static final int FLAG_TAB_WIRELESS_NETWORKS = 0;private static final int FLAG_TAB_DEVICE = 1;private static final int FLAG_TAB_PERSONAL = 2;private static final int FLAG_TAB_SYSTEM = 3;private final TabListener mTabListener = new TabListener() {@Overridepublic void onTabUnselected(Tab tab, FragmentTransaction ft) {// TODO Auto-generated method stub}@Overridepublic void onTabSelected(Tab tab, FragmentTransaction ft) {//Log.i("lwp-log", "tab.getContentDescription().toString() = "+tab.getContentDescription().toString());if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_wireless_networks))){mTabFlag = FLAG_TAB_WIRELESS_NETWORKS;}else if(tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_personal))) {mTabFlag = FLAG_TAB_PERSONAL;} else if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_system))) {mTabFlag = FLAG_TAB_SYSTEM;} else if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_device))) {mTabFlag = FLAG_TAB_DEVICE;}Log.i("lwp-log", "onTabSelected mTabFlag = " + mTabFlag); invalidateHeaders();}@Overridepublic void onTabReselected(Tab tab, FragmentTransaction ft) {}};private void setupWirelessNetworks(ActionBar bar) {final Tab tab = bar.newTab();View view = mInflater.inflate(R.layout.tab_widget_view, null);ImageView dialView = (ImageView)view.findViewById(R.id.main_activity_tab_image);if(dialView != null){dialView.setImageResource(R.drawable.ic_tab_wireless);}TextView dialText = (TextView) view.findViewById(R.id.main_activity_tab_text);if(dialText!=null){dialText.setText(R.string.header_category_wireless_networks);}tab.setCustomView(view);tab.setTabListener(mTabListener);tab.setContentDescription(R.string.header_category_wireless_networks);bar.addTab(tab);}private void setupDevice(ActionBar bar) {final Tab tab = bar.newTab();View view = mInflater.inflate(R.layout.tab_widget_view, null);ImageView dialView = (ImageView)view.findViewById(R.id.main_activity_tab_image);if(dialView != null){dialView.setImageResource(R.drawable.ic_tab_device);}TextView dialText = (TextView) view.findViewById(R.id.main_activity_tab_text);if(dialText!=null){dialText.setText(R.string.header_category_device);}tab.setCustomView(view);tab.setTabListener(mTabListener);tab.setContentDescription(R.string.header_category_device);bar.addTab(tab);}private void setupPersonal(ActionBar bar) {final Tab tab = bar.newTab();View view = mInflater.inflate(R.layout.tab_widget_view, null);ImageView dialView = (ImageView)view.findViewById(R.id.main_activity_tab_image);if(dialView != null){dialView.setImageResource(R.drawable.ic_tab_personal);}TextView dialText = (TextView) view.findViewById(R.id.main_activity_tab_text);if(dialText!=null){dialText.setText(R.string.header_category_personal);}tab.setCustomView(view);tab.setTabListener(mTabListener);tab.setContentDescription(R.string.header_category_personal);bar.addTab(tab);}private void setupSystem(ActionBar bar) {final Tab tab = bar.newTab();View view = mInflater.inflate(R.layout.tab_widget_view, null);ImageView dialView = (ImageView)view.findViewById(R.id.main_activity_tab_image);if(dialView != null){dialView.setImageResource(R.drawable.ic_tab_system);}TextView dialText = (TextView) view.findViewById(R.id.main_activity_tab_text);if(dialText!=null){dialText.setText(R.string.header_category_system);}tab.setCustomView(view);tab.setTabListener(mTabListener);tab.setContentDescription(R.string.header_category_system);bar.addTab(tab);}private LayoutInflater mInflater;//end by liweiping 20140103@Overrideprotected void onCreate(Bundle savedInstanceState) {if(UNIVERSEUI_SUPPORT){this.setTheme(R.style.Theme_Holo_new_ui);}if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) {getWindow().setUiOptions(0);}//start by liweiping 20140103setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);mInflater = getLayoutInflater();mTabFlag = FLAG_TAB_WIRELESS_NETWORKS;//end by liweiping 20140103getMetaData();mInLocalHeaderSwitch = true;super.onCreate(savedInstanceState);mInLocalHeaderSwitch = false;//For LowCost case, define the list selector by itself// if (LC_RAM_SUPPORT) {//ListView list = getListView();//list.setSelector(R.drawable.list_selector_holo_dark);// list.setOverScrollMode(View.OVER_SCROLL_NEVER);// }if (!onIsHidingHeaders() && onIsMultiPane()) {highlightHeader();// 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 stateif (savedInstanceState != null) {mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);}// If the current header was saved, switch to itif (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);}});}}/* Set ActionBar with popup function */protected void setActionBarStyle() {ActionBar actionBar = getActionBar();if (actionBar == null){return;}//start by liweiping 20140106Log.i("lwp-log", "this.toString() = " + this.toString());//if ( this.toString().contains("SubSettings") ) {if ( this.toString().contains("SubSettings") || this.toString().contains("$")) {//end by liweiping 20140106actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);actionBar.setDisplayHomeAsUpEnabled(true);}else {actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP^ ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);actionBar.setDisplayHomeAsUpEnabled(false);//start by liweiping 20140103actionBar.setAlternativeTabStyle(true);actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);if(actionBar.getTabCount() == 0){setupWirelessNetworks(actionBar);setupDevice(actionBar);setupPersonal(actionBar);setupSystem(actionBar);}final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);mTabFlag = settings.getInt("tab", FLAG_TAB_WIRELESS_NETWORKS);Log.i("lwp-log", "setActionBarStyle mTabFlag = " + mTabFlag);actionBar.selectTab(actionBar.getTabAt(mTabFlag));//end by liweiping 20140103}}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// TODO Add support for android.R.id.home in all Setting's onOptionsItemSelected// getActionBar().setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP,// ActionBar.DISPLAY_HOME_AS_UP);// Now it's done.if (item.getItemId() == android.R.id.home) {finish();return true;}return super.onOptionsItemSelected(item);}@Overrideprotected void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);// Save the current fragment, if it is the same as originally launchedif (mCurrentHeader != null) {outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);}if (mParentHeader != null) {outState.putParcelable(SAVE_KEY_PARENT_HEADER, mParentHeader);}}@Overridepublic void onResume() {ListAdapter listAdapter = getListAdapter();if (listAdapter instanceof HeaderAdapter) {((HeaderAdapter) listAdapter).resume();}setActionBarStyle();super.onResume();}@Overridepublic void onPause() {super.onPause();ListAdapter listAdapter = getListAdapter();if (listAdapter instanceof HeaderAdapter) {((HeaderAdapter) listAdapter).pause();}//start by liweiping 20140106final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);settings.edit().putInt("tab", settings.getInt("tab", FLAG_TAB_WIRELESS_NETWORKS)).commit();//end by liweiping 20140106 }//start by liweiping 20140106@Overrideprotected void onDestroy() {super.onDestroy();//final SharedPreferences settings = PreferenceManager// .getDefaultSharedPreferences(this);//settings.edit().putInt("tab", FLAG_TAB_WIRELESS_NETWORKS).commit();}//end by liweiping 20140106 @Overridepublic void onBackPressed() {if (!moveTaskToBack(false)) {super.onBackPressed();}}private void switchToHeaderLocal(Header header) {mInLocalHeaderSwitch = true;switchToHeader(header);mInLocalHeaderSwitch = false;}@Overridepublic void switchToHeader(Header header) {if (!mInLocalHeaderSwitch) {mCurrentHeader = null;mParentHeader = null;}super.switchToHeader(header);}/*** Switch to parent fragment and store the grand parent's info* @param className name of the activity wrapper for the parent fragment.*/private void switchToParent(String className) {final ComponentName cn = new ComponentName(this, className);try {final PackageManager pm = getPackageManager();final ActivityInfo parentInfo = pm.getActivityInfo(cn, PackageManager.GET_META_DATA);if (parentInfo != null && parentInfo.metaData != null) {String fragmentClass = parentInfo.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);CharSequence fragmentTitle = parentInfo.loadLabel(pm);Header parentHeader = new Header();parentHeader.fragment = fragmentClass;parentHeader.title = fragmentTitle;mCurrentHeader = parentHeader;switchToHeaderLocal(parentHeader);highlightHeader();mParentHeader = new Header();mParentHeader.fragment= parentInfo.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE);}} catch (NameNotFoundException nnfe) {Log.w(LOG_TAG, "Could not find parent activity : " + className);}}@Overridepublic void onNewIntent(Intent intent) {super.onNewIntent(intent);// If it is not launched from history, then reset to top-levelif ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0&& mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {switchToHeaderLocal(mFirstHeader);}}private void highlightHeader() {if (mTopLevelHeaderId != 0) {Integer index = mHeaderIndexMap.get(mTopLevelHeaderId);if (index != null) {getListView().setItemChecked(index, true);getListView().smoothScrollToPosition(index);}}}@Overridepublic Intent getIntent() {Intent superIntent = super.getIntent();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 methodif (startingFragment != null && !onIsMultiPane()) {Intent modIntent = new Intent(superIntent);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, superIntent.getExtras());return modIntent;}return superIntent;}/*** Checks if the component name in the intent is different from the Settings class and* returns the class name to load as a fragment.*/protected 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;}/*** Override initial header when an activity-alias is causing Settings to be launched* for a specific fragment encoded in the android:name parameter.*/@Overridepublic Header onGetInitialHeader() {String fragmentClass = getStartingFragmentClass(super.getIntent());if (fragmentClass != null) {Header header = new Header();header.fragment = fragmentClass;header.title = getTitle();header.fragmentArguments = getIntent().getExtras();mCurrentHeader = header;return header;}return mFirstHeader;}@Overridepublic Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,int titleRes, int shortTitleRes) {Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,titleRes, shortTitleRes);// some fragments want to avoid split actionbarif (DataUsageSummary.class.getName().equals(fragmentName) ||PowerUsageSummary.class.getName().equals(fragmentName) ||AccountSyncSettings.class.getName().equals(fragmentName) ||UserDictionarySettings.class.getName().equals(fragmentName)) {intent.putExtra(EXTRA_CLEAR_UI_OPTIONS, true);}intent.setClass(this, SubSettings.class);return intent;}/*** Populate the activity with the top-level headers.*/@Overridepublic void onBuildHeaders(List<Header> headers) {//start by liweiping 20130103//if(UNIVERSEUI_SUPPORT){// loadHeadersFromResource(R.xml.settings_headers_uui, headers);//}else{// loadHeadersFromResource(R.xml.settings_headers, headers);//}Log.i("lwp", "mTabFlag = "+mTabFlag + ", headers.size() = "+ headers.size() );if (mTabFlag == FLAG_TAB_WIRELESS_NETWORKS){loadHeadersFromResource(R.xml.settings_headers_wireless_networks, headers);}else if(mTabFlag == FLAG_TAB_DEVICE){loadHeadersFromResource(R.xml.settings_headers_device, headers);}else if(mTabFlag == FLAG_TAB_PERSONAL){loadHeadersFromResource(R.xml.settings_headers_personal, headers);}else{loadHeadersFromResource(R.xml.settings_headers_system, headers);}//end by liweiping 20130103updateHeaderList(headers);//start by liweiping 20130107 for bug 1146//mHeaders = headers;if(mHeaders != null){mHeaders.clear();mHeaders.addAll(headers);}//end by liweiping 20130107}private void updateHeaderList(List<Header> target) {int i = 0;while (i < target.size()) {Header header = target.get(i);// Ids are integers, so downcastingint id = (int) header.id;if (id == R.id.dock_settings) {if (!needsDockSettings())target.remove(header);} else if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);} else if (id == R.id.wifi_settings) {// Remove WiFi Settings if WiFi service is not available.// modified by zhangguixin// if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {if (!SystemProperties.getBoolean("ro.device.support.wifi", true)) {// if ("0".equals(SystemProperties.getBoolean("ro.device.support.wifi", "1"))) {target.remove(header);}} else if (id == R.id.bluetooth_settings) {// Remove Bluetooth Settings if Bluetooth service is not available.// modified by zhangguixin// if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {if (!SystemProperties.getBoolean("ro.device.support.bt", true)) {// if ("0".equals(SystemProperties.getBoolean("ro.device.support.bt", "1"))) {target.remove(header);}} else if (id == R.id.dual_sim_settings) {if (!TelephonyManager.isMultiSim()) {target.remove(header);}} else if (id == R.id.data_usage_settings &&"sc8825".equalsIgnoreCase(SystemProperties.get("ro.board.platform", "sc8825"))) {//fix bug182500target.remove(header);}//add by huangweiwei, topwise, 2013-10-12else if (id == R.id.development_settings) {if (android.os.TopwiseProp.getDefaultSettingBoolean("settings_remove_development", false)) {target.remove(header);}}//add end by huangweiwei, topwise, 2013-10-12// Increment if the current one wasn't removed by the Utils code.if (target.get(i) == header) {// Hold on to the first header, when we need to reset to the top-levelif (mFirstHeader == null &&HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {mFirstHeader = header;}mHeaderIndexMap.put(id, i);i++;}}}private boolean needsDockSettings() {return getResources().getBoolean(R.bool.has_dock_settings);}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 objectfinal 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}}@Overridepublic boolean hasNextButton() {return super.hasNextButton();}@Overridepublic Button getNextButton() {return super.getNextButton();}private static class HeaderAdapter extends ArrayAdapter<Header> {static final int HEADER_TYPE_CATEGORY = 0;static final int HEADER_TYPE_NORMAL = 1;static final int HEADER_TYPE_SWITCH = 2;private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1;private final WifiEnabler mWifiEnabler;private final BluetoothEnabler mBluetoothEnabler;private SparseArray<View> mViewCache;private static class HeaderViewHolder {ImageView icon;TextView title;TextView summary;Switch switch_;}private LayoutInflater mInflater;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) {return HEADER_TYPE_SWITCH;} else {return HEADER_TYPE_NORMAL;}}@Overridepublic int getItemViewType(int position) {Header header = getItem(position);return getHeaderType(header);}//start by liweiping 20140106@Overridepublic void notifyDataSetChanged() {// TODO Auto-generated method stubsuper.notifyDataSetChanged();mViewCache = new SparseArray<View>(getCount());}//end by liweiping 20140106@Overridepublic boolean areAllItemsEnabled() {return false; // because of categories}@Overridepublic boolean isEnabled(int position) {return getItemViewType(position) != HEADER_TYPE_CATEGORY;}@Overridepublic int getViewTypeCount() {return HEADER_TYPE_COUNT;}@Overridepublic boolean hasStableIds() {return true;}public HeaderAdapter(Context context, List<Header> objects) {super(context, 0, objects);mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);// Temp Switches provided as placeholder until the adapter replaces these with actual// Switches inflated from their layouts. Must be done before adapter is set in supermWifiEnabler = new WifiEnabler(context, new Switch(context));mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));mViewCache = new SparseArray<View>(objects.size());}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {HeaderViewHolder holder;Header header = getItem(position);int headerType = getHeaderType(header);View view = null;convertView = mViewCache.get(position);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_NORMAL:view = mInflater.inflate(com.android.internal.R.layout.preference_header_item, parent,false);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);break;}view.setBackgroundDrawable(null);view.setTag(holder);mViewCache.put(position, view);} else {view = convertView;holder = (HeaderViewHolder) view.getTag();return view;}// All view fields must be updated every time, because the view may be recycledswitch (headerType) {case HEADER_TYPE_CATEGORY:holder.title.setText(header.getTitle(getContext().getResources()));holder.title.setTextColor(android.R.color.transparent);//add by liweiping 20130826break;case HEADER_TYPE_SWITCH:// Would need a different treatment if the main menu had more switchesif (header.id == R.id.wifi_settings) {mWifiEnabler.setSwitch(holder.switch_);} else {mBluetoothEnabler.setSwitch(holder.switch_);}// No break, fall through on purpose to update common fields//$FALL-THROUGH$case HEADER_TYPE_NORMAL: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);}break;}//start by liweiping 20130826if (header.fragment == null && header.intent == null) {view.setBackgroundColor(android.R.color.transparent); }else if(header.id == R.id.dual_sim_settings || header.id == R.id.sound_settings || header.id == R.id.sync_settings || header.id == R.id.dock_settings || header.id == R.id.date_time_settings){view.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_top);}else if(header.id == R.id.wireless_settings || header.id == R.id.manufacturer_settings || header.id == R.id.privacy_settings || header.id == R.id.about_settings || header.id == R.id.application_settings){view.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_bottom);}else {view.setBackgroundResource(com.android.internal.R.drawable.easy_pref_item_center);}//end by liweiping 20130826return view;}public void resume() {mWifiEnabler.resume();mBluetoothEnabler.resume();}public void pause() {mWifiEnabler.pause();mBluetoothEnabler.pause();}}@Overridepublic boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {// Override the fragment title for Wallpaper settingsint titleRes = pref.getTitleRes();if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {titleRes = R.string.wallpaper_settings_fragment_title;}startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, null, null, 0);return true;}//start,added by topwise hehuadong in 2013.10.24@Overridepublic void onHeaderClick(Header header, int position) {// TODO Auto-generated method stub//start by liweiping 20140105final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);settings.edit().putInt("tab", mTabFlag).commit();Log.i("lwp-log", "onHeaderClick..." + settings.getInt("tab", -1));//end by liweiping 20140105if (TopwiseProp.getDefaultSettingString("default_customize_about_device")!=null){if (header != null && header.fragment !=null && header.fragment.equals("com.android.settings.DeviceInfoSettings")){header.fragment="com.android.settings.AboutDeviceSettings";}}super.onHeaderClick(header, position);}//end,added by topwise hehuadong in 2013.10.24@Overridepublic void setListAdapter(ListAdapter adapter) {if (mHeaders == null) {mHeaders = new ArrayList<Header>();// When the saved state provides the list of headers, onBuildHeaders is not called// Copy the list of Headers from the adapter, preserving their orderfor (int i = 0; i < adapter.getCount(); i++) {mHeaders.add((Header) adapter.getItem(i));}}// Ignore the adapter provided by PreferenceActivity and substitute ours insteadsuper.setListAdapter(new HeaderAdapter(this, mHeaders));}/** Settings subclasses for launching independently.*/public static class BluetoothSettingsActivity extends Settings { /* empty */ }public static class WirelessSettingsActivity extends Settings { /* empty */ }public static class TetherSettingsActivity extends Settings { /* empty */ }public static class VpnSettingsActivity extends Settings { /* empty */ }public static class DateTimeSettingsActivity extends Settings { /* empty */ }public static class StorageSettingsActivity extends Settings { /* empty */ }public static class WifiSettingsActivity extends Settings { /* empty */ }public static class WifiP2pSettingsActivity extends Settings { /* empty */ }public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }public static class SpellCheckersSettingsActivity extends Settings { /* empty */ }public static class LocalePickerActivity extends Settings { /* empty */ }public static class UserDictionarySettingsActivity extends Settings { /* empty */ }public static class SoundSettingsActivity extends Settings { /* empty */ }public static class DisplaySettingsActivity extends Settings { /* empty */ }public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }public static class ApplicationSettingsActivity extends Settings { /* empty */ }public static class ManageApplicationsActivity extends Settings { /* empty */ }public static class StorageUseActivity extends Settings { /* empty */ }public static class DevelopmentSettingsActivity extends Settings { /* empty */ }public static class AccessibilitySettingsActivity extends Settings { /* empty */ }public static class SecuritySettingsActivity extends Settings { /* empty */ }public static class LocationSettingsActivity extends Settings { /* empty */ }public static class PrivacySettingsActivity extends Settings { /* empty */ }public static class DockSettingsActivity extends Settings { /* empty */ }public static class RunningServicesActivity extends Settings { /* empty */ }public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }public static class PowerUsageSummaryActivity extends Settings { /* empty */ }public static class AccountSyncSettingsActivity extends Settings { /* empty */ }public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }public static class DataUsageSummaryActivity extends Settings { /* empty */ }public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }public static class AndroidBeamSettingsActivity extends Settings { /* empty */ }
}
简单分析一下:
①.首先我们在onResume中找到setActionBarStyle这个函数,这就是设置每次恢复时的必经之路,在这里,我们改变ActionBar的样式,如果是一级界面,就加上tab风格,二级界面就去掉tab风格,显示返回键。
/* Set ActionBar with popup function */protected void setActionBarStyle() {ActionBar actionBar = getActionBar();if (actionBar == null){return;}//start by liweiping 20140106Log.i("lwp-log", "this.toString() = " + this.toString());//if ( this.toString().contains("SubSettings") ) {if ( this.toString().contains("SubSettings") || this.toString().contains("$")) {//end by liweiping 20140106actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);actionBar.setDisplayHomeAsUpEnabled(true);}else {actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP^ ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);actionBar.setDisplayHomeAsUpEnabled(false);//start by liweiping 20140103actionBar.setAlternativeTabStyle(true);actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);if(actionBar.getTabCount() == 0){setupWirelessNetworks(actionBar);setupDevice(actionBar);setupPersonal(actionBar);setupSystem(actionBar);}final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);mTabFlag = settings.getInt("tab", FLAG_TAB_WIRELESS_NETWORKS);Log.i("lwp-log", "setActionBarStyle mTabFlag = " + mTabFlag);actionBar.selectTab(actionBar.getTabAt(mTabFlag));//end by liweiping 20140103}}
②.监听tab的选择事件,点击每一个tab产生对应的事件,然后把选择项存为全局变量mTabFlag中,最后调用invalidateHeaders()函数刷新界面,替换成不同的Header list。
private final TabListener mTabListener = new TabListener() {@Overridepublic void onTabUnselected(Tab tab, FragmentTransaction ft) {// TODO Auto-generated method stub}@Overridepublic void onTabSelected(Tab tab, FragmentTransaction ft) {//Log.i("lwp-log", "tab.getContentDescription().toString() = "+tab.getContentDescription().toString());if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_wireless_networks))){mTabFlag = FLAG_TAB_WIRELESS_NETWORKS;}else if(tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_personal))) {mTabFlag = FLAG_TAB_PERSONAL;} else if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_system))) {mTabFlag = FLAG_TAB_SYSTEM;} else if (tab.getContentDescription().toString().equals(getResources().getString(R.string.header_category_device))) {mTabFlag = FLAG_TAB_DEVICE;}Log.i("lwp-log", "onTabSelected mTabFlag = " + mTabFlag);invalidateHeaders();}@Overridepublic void onTabReselected(Tab tab, FragmentTransaction ft) {}};
③.当调用invalidateHeaders()函数时,就进入了onBuildHeaders(List<Header> headers)函数,在这里,我们需要根据mTabFlag来loadHeadersFromResource对应的布局文件,最后一定要记住用新的headers将全局变量mHeaders替换掉。
/*** Populate the activity with the top-level headers.*/@Overridepublic void onBuildHeaders(List<Header> headers) {//start by liweiping 20130103//if(UNIVERSEUI_SUPPORT){// loadHeadersFromResource(R.xml.settings_headers_uui, headers);//}else{// loadHeadersFromResource(R.xml.settings_headers, headers);//}Log.i("lwp", "mTabFlag = "+mTabFlag + ", headers.size() = "+ headers.size() );if (mTabFlag == FLAG_TAB_WIRELESS_NETWORKS){loadHeadersFromResource(R.xml.settings_headers_wireless_networks, headers);}else if(mTabFlag == FLAG_TAB_DEVICE){loadHeadersFromResource(R.xml.settings_headers_device, headers);}else if(mTabFlag == FLAG_TAB_PERSONAL){loadHeadersFromResource(R.xml.settings_headers_personal, headers);}else{loadHeadersFromResource(R.xml.settings_headers_system, headers);}//end by liweiping 20130103updateHeaderList(headers);//start by liweiping 20130107 for bug 1146//mHeaders = headers;if(mHeaders != null){mHeaders.clear();mHeaders.addAll(headers);}//end by liweiping 20130107}
OK,今天就大概这样了。