公司最近在做一个feature phone项目,基于Android R smart phone的分支,改成feature phone,我被分到了settings模块
先看一下做成的效果
一级菜单:
二级菜单:
子界面:
switch界面:
1.首先阅读一下Settings的代码结构
Settings其实并不仅仅是packages/apps/Settings/ 以下几个成员也是Settings的重要组成部分
Settings SettingsLib SettingsProvider settings.db
SettingsLib可以理解为Settings使用的一个lib库。跟Settings只是放在了两个位置,基本可以认为是一体的
SettingsProvider又是做什么的呢?其实看到这个名字我们不难猜到他扮演着什么样的角色。SettingsProvider继承ContentProvider,ContentProvider在android中主要扮演着数据共享的角色。SettingsProvider中有一个数据库,并且这个数据库是对外公开的。
Settings与SettingsProvider之间又是什么关系呢?且看下面分析。他们之间存在什么联系呢?
其实,Settings会对SettingsProvider中的数据库进行操作和监听。Settings中大部分选项都会涉及到对SettingsProvider的操作。通过跟踪代码发现,Settings大部分操作的就是SettingsProvider中的数据,也有一些直接操作系统属性的等等。
当用户在修改系统设置时,大部分实际上是在修改SettingsProvider中的值。
当SettingsProvider数据库中的值被改变时,一些系统服务什么的就会监听到,这时候就会通过jni等当时操作底层,从而达到系统属性或配置改变的效果。
知道这些,我们对Settings就大概心里有点数了。
1.1 Settings的启动流程
settings默认启动Settings,
百度一下,activity-alias作为一个已存在Activity的别名,则应该可以通过该别名标签声明快速打开目标Activity。因此activity-alias可用来设置某个Activity的快捷入口,可以放在桌面上或者通过该别名被其他组件快速调起。该标签元素支持一些属性及intent-filter、meta-data等配置,因此可以触发一些跟目标Activity不同的功能逻辑,虽然打开的是同一个Activity。举个简单的例子,如之前需要先打开主界面,然后才能点击进入某个Activity,如果使用activity-alias为该Activity配置一个快捷入口,甚至可以为其在桌面生成一个图标,然后点击桌面图标可直接进入该Activity,该功能可满足某些需要快速到达功能界面的需求。
所以我们启动的是android:targetActivity=".homepage.SettingsHomepageActivity"
这里有个疑问,如果是单纯一个入口,为什么还要写个 settings>SettingsActivity>SettingsBaseActivity>FragmentActivity?
先看下SettingsHomepageActivity
Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载;
每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
xml中配置preference时,必须定义”android:key“属性;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
root.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setHomepageContainerPaddingTop();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
主界面布局中主要包含两部分:一个顶部快捷搜索栏,一个Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。
可以看到一级菜单启动的是TopLevelSettings,TopLevelSettings继承于DashboardFragment.java:
public class TopLevelSettings extends DashboardFragment implements
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
TopLevelSettings的构造方法:
public TopLevelSettings() {
final Bundle args = new Bundle();
// Disable the search icon because this page uses a full search view in actionbar.
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
setArguments(args);
}
可以看到通过构造方法传递了一个参数,从注释中可以看出,该参数的用意是由于主界面使用完整的搜索视图所以在主界面的actionbar中隐藏了搜索图标。然后再根据framgments生命周期先来看onAttach()方法:
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SupportPreferenceController.class).setActivity(getActivity());
}
调用父类DashboardFragment.java的onAttach()方法:
@Override
public void onAttach(Context context) {
super.onAttach(context);
mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider = FeatureFactory.getFactory(context).
getDashboardFeatureProvider(context);
// Load preference controllers from code
final List controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
mControllers.addAll(controllersFromCode);
}
mControllers.addAll(uniqueControllerFromXml);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml.forEach(controller -> {
if (controller instanceof LifecycleObserver) {
lifecycle.addObserver((LifecycleObserver) controller);
}
});
// Set metrics category for BasePreferenceController.
final int metricCategory = getMetricsCategory();
mControllers.forEach(controller -> {
if (controller instanceof BasePreferenceController) {
((BasePreferenceController) controller).setMetricsCategory(metricCategory);
}
});
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
mControllers.add(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : mControllers) {
addPreferenceController(controller);
}
}
这里就是为什么xml里面可以定义
controller:
就是在这里解析的
DashboardFragment.java的onCreate()方法:
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set ComparisonCallback so we get better animation when list changes.
getPreferenceManager().setPreferenceComparisonCallback(
new PreferenceManager.SimplePreferenceComparisonCallback());
if (icicle != null) {
// Upon rotation configuration change we need to update preference states before any
// editing dialog is recreated (that would happen before onResume is called).
updatePreferenceStates();
}
}
设置ComparisonCallback,以便在列表更改时获得更好的动画效果。
第一次进入时,icicle为null,根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
checkUiBlocker(mControllers);
refreshAllPreferences(getLogTag());
mControllers.stream()
.map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
.filter(Objects::nonNull)
.forEach(preference -> {
// Give all controllers a chance to handle click.
preference.getExtras().putInt(CATEGORY, getMetricsCategory());
});
}
调用refreshAllPreferences():
private void refreshAllPreferences(final String tag) {
final PreferenceScreen screen = getPreferenceScreen();
// First remove old preferences.
if (screen != null) {
// Intentionally do not cache PreferenceScreen because it will be recreated later.
screen.removeAll();
}
// Add resource based tiles.
displayResourceTiles();
refreshDashboardTiles(tag);
final Activity activity = getActivity();
if (activity != null) {
Log.d(tag, "All preferences added, reporting fully drawn");
activity.reportFullyDrawn();
}
updatePreferenceVisibility(mPreferenceControllers);
}
刷新所有preference items,包括来自xml的静态preference和来自DashboardCategory的动态preference,静态xml定义的prefs(调用displayResourceTiles()方法),动态DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。
displayResourceTiles():此方法主要是从xml资源文件中加载显示prefs:
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
screen.setOnExpandButtonClickListener(this);
displayResourceTilesToScreen(screen);
}
protected void displayResourceTilesToScreen(PreferenceScreen screen) {
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
}
这里加载有两种方法
1.2 静态加载
首先调用getPreferenceScreenResId()方法获取所要加载的xml的ID,然后调用子类TopLevelSettings.java的getPreferenceScreenResId()方法:
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
可以看到Settings主界面加载的xml文件是top_level_settings:
主要配置的是一些Preference菜单项如网络和互联网、已连接的设备、应用和通知、电池等等,Preference的配置含义:
key:唯一性ID;
title:标题;
summary:简介;
ico:图标;
order:加载显示优先级,order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后;
fragment:点击此preference所跳转的fragment界面;
controller:控制管理类。
1.3 动态加载
refreshDashboardTiles
动态加载项通过mDashboardFeatureProvider.getTilesForCategory()来获取,需要传入一个key值作为参数。
key的来历
首先看一下这个key是怎么来的呢?通过调用getCategoryKey()获得
public String getCategoryKey() {
return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP是一个静态变量,在类的静态代码块中添加了数据:
public class DashboardFragmentRegistry {
/**
* Map from parent fragment to category key. The parent fragment hosts child with
* category_key.
*/
public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
...
static {
PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
CategoryKey.CATEGORY_HOMEPAGE);
...
}
再来看看CategoryKey.CATEGORY_HOMEPAGE
public final class CategoryKey {
// Activities in this category shows up in Settings homepage.
public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
...
}
因此TopLevelSettings类查找的是com.android.settings.category.ia.homepage。
所以在manifest里面配置了就会被加载
DashboardFeatureProvider的实现类是DashboardFeatureProviderImpl, 它实现了getTilesForCategory(), 最终还是通过CategoryManager来获取Category。
public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
...
private final CategoryManager mCategoryManager;
...
public DashboardFeatureProviderImpl(Context context) {
...
// CategoryManager是一个单例
mCategoryManager = CategoryManager.get(context);
...
}
@Override
public DashboardCategory getTilesForCategory(String key) {
// 查找动态项
return mCategoryManager.getTilesByCategory(mContext, key);
}
在CategoryManager中首先进行加载,然后保存在mCategoryByKeyMap中,最后通过key值获取对应的加载项
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
// 1,加载
tryInitCategories(context);
// 2,读取
return mCategoryByKeyMap.get(categoryKey);
}
private synchronized void tryInitCategories(Context context) {
// Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
// happens.
tryInitCategories(context, false /* forceClearCache */);
}
private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
if (mCategories == null) {
if (forceClearCache) {
mTileByComponentCache.clear();
}
mCategoryByKeyMap.clear();
// 加载
mCategories = TileUtils.getCategories(context, mTileByComponentCache);
for (DashboardCategory category : mCategories) {
// 根据不同的key保存在map中
mCategoryByKeyMap.put(category.key, category);
}
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
// 排序
sortCategories(context, mCategoryByKeyMap);
// 过滤重复
filterDuplicateTiles(mCategoryByKeyMap);
}
}
其关键代码为TileUtils.getCategories(context, mTileByComponentCache)。
其中TileUtils位于SettingsLib中,包名为:com.android.settingslib.drawer.TileUtils,代码如下:
/**
* Build a list of DashboardCategory.
*/
public static List<DashboardCategory> getCategories(Context context,
Map<Pair<String, String>, Tile> cache) {
final long startTime = System.currentTimeMillis();
final boolean setup =
Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
// 保存加载到的tile
final ArrayList<Tile> tiles = new ArrayList<>();
final UserManager userManager = (UserManager) context.getSystemService(
Context.USER_SERVICE);
for (UserHandle user : userManager.getUserProfiles()) {
// TODO: Needs much optimization, too many PM queries going on here.
if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
// Only add Settings for this user.
// 各种load
loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
OPERATOR_DEFAULT_CATEGORY, tiles, false);
loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
}
if (setup) {
loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
}
}
final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
// 遍历加载到的tile
for (Tile tile : tiles) {
final String categoryKey = tile.getCategory();
DashboardCategory category = categoryMap.get(categoryKey);
if (category == null) {
category = new DashboardCategory(categoryKey);
if (category == null) {
Log.w(LOG_TAG, "Couldn't find category " + categoryKey);
continue;
}
categoryMap.put(categoryKey, category);
}
category.addTile(tile);
}
final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
for (DashboardCategory category : categories) {
// 排序
category.sortTiles();
}
if (DEBUG_TIMING) {
Log.d(LOG_TAG, "getCategories took "
+ (System.currentTimeMillis() - startTime) + " ms");
}
return categories;
}
注意上面定义的final ArrayList<Tile> tiles = new ArrayList<>(); 这个List被传到后面的几个方法中,最终将加载到的内容放入其中。
根据不同action进行加载
static void loadTilesForAction(Context context,
UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
final Intent intent = new Intent(action);
if (requireSettings) {
intent.setPackage(SETTING_PKG);
}
// 分两种情况
loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
}
根据PackageManager 获取应用的配置文件信息。
private static void loadActivityTiles(Context context,
UserHandle user, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, List<Tile> outTiles, Intent intent) {
final PackageManager pm = context.getPackageManager();
final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
final ActivityInfo activityInfo = resolved.activityInfo;
final Bundle metaData = activityInfo.metaData;
// 加载tile
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
}
}
private static void loadProviderTiles(Context context,
UserHandle user, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, List<Tile> outTiles, Intent intent) {
final PackageManager pm = context.getPackageManager();
final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
0 /* flags */, user.getIdentifier());
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
final ProviderInfo providerInfo = resolved.providerInfo;
final List<Bundle> switchData = getSwitchDataFromProvider(context,
providerInfo.authority);
if (switchData == null || switchData.isEmpty()) {
continue;
}
for (Bundle metaData : switchData) {
// 加载tile
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
providerInfo);
}
}
}
加载到的结果放入outTiles中,也就是前面定义的List中。
private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
ComponentInfo componentInfo) {
String categoryKey = defaultCategory;
// Load category
if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
+ intent + " missing metadata "
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
return;
} else {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
final boolean isProvider = componentInfo instanceof ProviderInfo;
final Pair<String, String> key = isProvider
? new Pair<>(((ProviderInfo) componentInfo).authority,
metaData.getString(META_DATA_PREFERENCE_KEYHINT))
: new Pair<>(componentInfo.packageName, componentInfo.name);
Tile tile = addedCache.get(key);
if (tile == null) {
tile = isProvider
? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
: new ActivityTile((ActivityInfo) componentInfo, categoryKey);
addedCache.put(key, tile);
} else {
tile.setMetaData(metaData);
}
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
到此动态项查找及获取已经完成,那么查找到后怎么显示出来的呢?
此处只是DashboardFragment中进行动态加载的逻辑,在继承自SettingsActivity的Activity中也会进行动态加载,核心逻辑是相同的,这里不做深入。
显示动态加载项
继续看refreshDashboardTiles()
/**
* Refresh preference items backed by DashboardCategory.
*/
private void refreshDashboardTiles(final String tag) {
final PreferenceScreen screen = getPreferenceScreen();
// 加载数据
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
Log.d(tag, "NO dashboard tiles for " + tag);
return;
}
final List<Tile> tiles = category.getTiles();
if (tiles == null) {
Log.d(tag, "tile list is empty, skipping category " + category.key);
return;
}
// Create a list to track which tiles are to be removed.
final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);
// Install dashboard tiles.
final boolean forceRoundedIcons = shouldForceRoundedIcon();
// 遍历加载项
for (Tile tile : tiles) {
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
if (TextUtils.isEmpty(key)) {
Log.d(tag, "tile does not contain a key, skipping " + tile);
continue;
}
if (!displayTile(tile)) {
continue;
}
if (mDashboardTilePrefKeys.containsKey(key)) {
// Have the key already, will rebind.
final Preference preference = screen.findPreference(key);
mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),
forceRoundedIcons, getMetricsCategory(), preference, tile, key,
mPlaceholderPreferenceController.getOrder());
} else {
// Don't have this key, add it.
// 1,创建Preference
final Preference pref = createPreference(tile);
final List<DynamicDataObserver> observers =
mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),
forceRoundedIcons, getMetricsCategory(), pref, tile, key,
mPlaceholderPreferenceController.getOrder());
// 2,添加到screen
screen.addPreference(pref);
registerDynamicDataObservers(observers);
mDashboardTilePrefKeys.put(key, observers);
}
remove.remove(key);
}
// Finally remove tiles that are gone.
for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
final String key = entry.getKey();
mDashboardTilePrefKeys.remove(key);
final Preference preference = screen.findPreference(key);
if (preference != null) {
screen.removePreference(preference);
}
unregisterDynamicDataObservers(entry.getValue());
}
}
获取加载到的数据后进行遍历,判断当前列表中是否存在该设置项,不存在的话就创建
通过createPreference()创建Preference如下。
Preference createPreference(Tile tile) {
return tile instanceof ProviderTile
? new SwitchPreference(getPrefContext())
: tile.hasSwitch()
? new MasterSwitchPreference(getPrefContext())
: new Preference(getPrefContext());
}
最终动态添加到screen中:
screen.addPreference(pref);
1.4 启动子界面
这里发现原来哇纠结的activity-alias android:name="Settings" 跟Settings.java并不是同一个东西,害
Settings.java是把所有的子界面activity 都写成空Activity,这里的空Activity指的是没有重写7大生命周期方法
Settings的avtivty 继承的是FragmentActivity,android R上是 androidx.fragment.app.FragmentActivity;
这里preference 跳转到子界面可以使用几种方式,
1.
<intent
android:action="android.intent.action.MAIN"
android:targetClass="com.android.settings.accessibility.cn.ReadOutActivity"
android:targetPackage="com.android.settings" />
2.
android:fragment="com.android.settings.display.ToggleFontSizePreferenceFragment"
3.
或者是在对应的Fragment 自己start
至于子界面具体的逻辑这边就不深入了
2.Smart phone 2 feature phone
回到主题,我们来看看怎么修改为feature phone 的风格
1.去掉prefence的icon
有两种方式,一种在TopLevelSettings.java 里面遍历PreferenceScreen @+android:id/icon 把这个icon都Gone掉,另一种是新建个xml 重新写个一级菜单的xml,由于我们项目跟原生改动比较大,这边用的是后者
2.prefence前面加上数字
addPreferencesFromResource的时候 title加上数字
private void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
if (preferenceGroup == null) return;
for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
Preference pref = preferenceGroup.getPreference(i);
if (pref instanceof PreferenceGroup) {
checkAvailablePrefs((PreferenceGroup) pref);
}
pref.setShouldShowTitleNumber(true);
CharSequence summary = pref.getSummary();
if(!TextUtils.isEmpty(summary)) {
//summary = " "+summary;
pref.setSummary(summary);
}
CharSequence mTitle = pref.getTitle();
if(i == 11){
mTitle = "*. "+mTitle;
}else if(i ==12){
mTitle = "#. "+mTitle;
}else{
mTitle = ((i+1)%10)+". "+mTitle;
}
pref.setTitle(mTitle);
}
}
Preference.java 把mSummary前面加点空格
if (mShouldShowTitleNumber) {
mSummary = " " + summary;
}
3.按数字的时候,启动数字对应的preference
getListView().setOnKeyListener(mListOnKeyListener);
getListView().setListViewCanRespondNumberKey(true);
getListView().setListViewCanCycleScroll(true);
}
private View.OnKeyListener mListOnKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Object selectedItem = mList.getSelectedItem();
if (selectedItem instanceof Preference) {
View selectedView = mList.getSelectedView();
return ((Preference)selectedItem).onKey(
selectedView, keyCode, event);
}
return false;
}
};
4.Activty 使用对应的Style
5.去掉原生的actionbar
Settings/src/com/android/settings/core/SettingsBaseActivity.java
/*if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
toolbar.setVisibility(View.GONE);
return;
}
setActionBar(toolbar);*/
6.加上下面的导航键
添加一个activity requestWindowFeature(Window.FEATURE_CUST_SCREEN);
如果有这个feature 在这里返回添加了导航layout的id
mContentParent = generateLayout(mDecor);
这样不会影响别的继承activity的子类
activity 添加对导航三个textview的设置
public void setMiddleButtonText(int id) {
getWindow().getWindowBottomOperationBar().setMiddleButtonText(id);
}
7.子界面都要改成feature phone 风格
一级菜单都是prefence 普通的风格,有上面的修改已经可以满足。
二级菜单 有switch,改成普通的prefence,跳转到上面的switch界面选择,选择之后,弹出feature风格的dialog即可,Summary显示选择结果即可
三级甚至四级菜单,改成统一白色,的列表风格这样就基本完成了
8.android.bp添加代码以及res对应的目录,这样阅读起来比较方便,知道自己添加了哪些东西
resource_dirs: [
"res-feature-phone",
],
srcs: [
"tests/src/**/*.kt",
"tests/src/**/*.java",
"src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
],
一般src目录不需要额外添加
实际上的修改会有非常非常多,我这边改bug就改了一两个月。
害,希望对大家有帮助!