Android R Settings Smart phone 2 feature phone

本文详细解析了Android系统中Settings模块的工作原理,从代码结构到启动流程,再到如何针对feature phone进行定制修改。介绍了Settings模块中各组件的作用及其交互方式,并提供了具体实例说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

公司最近在做一个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就改了一两个月。

害,希望对大家有帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值