Android7.0 & 8.0 Language Settings 定制

这里主要介绍如何去定制Settings中的语言,7.0以后Settings中的Languages & Input这项有了一些变化,不再是直接把语言列表显示出来了,而是下面这个界面。这里写图片描述

选择了Add a language就会把所以的语言都列出来

这里写图片描述
再选择简体中文还会弹出一个列表
这里写图片描述
选择完一个语言后,在最开始的列表中就会多一个语言出来。
这里写图片描述

这里同时可以设置多个语言,在APK中设置语言时就会体现出来了。上面这些图片是我定制过的,删除了很多语言,所以就剩这几个了。


下面来看如何去定制的。

1.在AndroidMainfest.xml中找到Language Settings

        <activity android:name="Settings$InputMethodAndLanguageSettingsActivity"
            android:label="@string/language_keyboard_settings_title"
            android:icon="@drawable/ic_settings_language"
            android:taskAffinity="com.android.settings"
            android:parentActivityName="Settings">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter android:priority="-1">
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.category"
                android:value="com.android.settings.category.personal" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.inputmethod.InputMethodAndLanguageSettings" />
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                android:value="true" />
        </activity>

这里的InputMethodAndLanguageSettingsActivity是个别名,真正的是android:value=”com.android.settings.inputmethod.InputMethodAndLanguageSettings”。

2.InputMethodAndLanguageSettings分析
我们只关注语言设置,所以找到对应的语言设置就行了,其它不看。

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        addPreferencesFromResource(R.xml.language_settings);

打开这个布局文件,来寻找我们要找的语言设置。

3.language_settings分析
打开这个布局,第一个PreferenceScreen就是我们要找的语言设置界面。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
        android:key="language_keyboard_settings"
        android:title="@string/language_keyboard_settings_title">

    <PreferenceScreen
            android:key="phone_language"
            android:title="@string/phone_language"
            android:fragment="com.android.settings.localepicker.LocaleListEditor"
            />

4.LocaleListEditor分析
这个类就是我们之前看到了,有个Add a language按钮的那张图片上的实现。我们要看这个按钮是怎么去加载语言的,所以要找到对应的按钮点击事件。这是个SettingsPreferenceFragment,我们先看onCreateView。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) {
        final View result = super.onCreateView(inflater, container, savedInstState);
        final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result);

        getActivity().setTitle(R.string.pref_title_lang_selection);

        configureDragAndDrop(myLayout);
        return result;
    }

这里有加载一个布局文件,上面是显示的语言,下面就是那个按钮。

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layoutDirection="locale"
            android:textDirection="locale">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingTop="8dp"
        android:paddingBottom="8dp">

        <com.android.settings.localepicker.LocaleRecyclerView
            android:id="@+id/dragList"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"/>

        <Button
            android:id="@+id/add_language"
            android:layout_width="match_parent"
            android:layout_height="?android:listPreferredItemHeight"
            android:paddingStart="?android:attr/listPreferredItemPaddingStart"
            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
            android:drawableStart="@drawable/ic_add_24dp"
            android:drawablePadding="24dp"
            android:textAlignment="textStart"
            android:text="@string/add_a_language"
            style="@style/Base.Widget.AppCompat.Button.Borderless"
            android:textAppearance="?android:attr/textAppearanceListItem"/>

    </LinearLayout>

</ScrollView>

然后就去LocaleListEditor中寻找add_language这个按钮的点击事件。看它是如何加载语言列表并显示的。

    private void configureDragAndDrop(View view) {
        final RecyclerView list = (RecyclerView) view.findViewById(R.id.dragList);
        final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
        llm.setAutoMeasureEnabled(true);
        list.setLayoutManager(llm);

        list.setHasFixedSize(true);
        mAdapter.setRecyclerView(list);
        list.setAdapter(mAdapter);

        mAddLanguage = view.findViewById(R.id.add_language);
        mAddLanguage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker(
                        getContext(), LocaleListEditor.this, false /* translate only */);
                getFragmentManager()
                        .beginTransaction()
                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                        .replace(getId(), selector)
                        .addToBackStack("localeListEditor")
                        .commit();
            }
        });
    }

这边是新建了一个LocalePickerWithRegion对象,然后把它作为新的fragment来显示,所以就是这里去加载并显示语言列表的。

5.LocalePickerWithRegion分析
LocalePickerWithRegion是一个ListFragment,所以找到对应的adapter就可以了。上面的LocalePickerWithRegion对象是通过createLanguagePicker创建的,我们来看下这个函数。

    public static LocalePickerWithRegion createLanguagePicker(Context context,
            LocaleSelectedListener listener, boolean translatedOnly) {
        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
        localePicker.setListener(context, listener, /* parent */ null, translatedOnly);
        return localePicker;
    }

没什么,就是去new了一个对象,直接看onCreate。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);

        if (mLocaleList == null) {
            // The fragment was killed and restored by the FragmentManager.
            // At this point we have no data, no listener. Just return, to prevend a NPE.
            // Fixes b/28748150. Created b/29400003 for a cleaner solution.
            returnToParentFrame();
            return;
        }

        final boolean countryMode = mParentLocale != null;
        final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode);
        final LocaleHelper.LocaleInfoComparator comp =
                new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
        mAdapter.sort(comp);
        setListAdapter(mAdapter);
    }

这里回去setListAdapter,给这个ListFragment设置adapter。我们已经找到了这个adapter,然后看它怎么被赋值的。mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode); 就返回一个SuggestedLocaleAdapter,所以要看mLocaleList,语言列表都在这里面。去看mLocaleList是怎么被赋值的,直接搜索这个关键字。


    /**
     * Sets the listener and initializes the locale list.
     *
     * <p>Returns true if we need to show the list, false if not.</p>
     *
     * <p>Can return false because of an error, trying to show a list of countries,
     * but no parent locale was provided.</p>
     *
     * <p>It can also return false if the caller tries to show the list in country mode and
     * there is only one country available (i.e. Japanese => Japan).
     * In this case we don't even show the list, we call the listener with that locale,
     * "pretending" it was selected, and return false.</p>
     */
    private boolean setListener(Context context, LocaleSelectedListener listener,
            LocaleStore.LocaleInfo parent, boolean translatedOnly) {
        this.mParentLocale = parent;
        this.mListener = listener;
        this.mTranslatedOnly = translatedOnly;
        setRetainInstance(true);

        final HashSet<String> langTagsToIgnore = new HashSet<>();
        if (!translatedOnly) {
            final LocaleList userLocales = LocalePicker.getLocales();
            final String[] langTags = userLocales.toLanguageTags().split(",");
            Collections.addAll(langTagsToIgnore, langTags);
        }

        if (parent != null) {
            mLocaleList = LocaleStore.getLevelLocales(context,
                    langTagsToIgnore, parent, translatedOnly);
            if (mLocaleList.size() <= 1) {
                if (listener != null && (mLocaleList.size() == 1)) {
                    listener.onLocaleSelected(mLocaleList.iterator().next());
                }
                return false;
            }
        } else {
            mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
                    null /* no parent */, translatedOnly);
        }

        //aaron
        for(LocaleStore.LocaleInfo lf : mLocaleList) {
            Log.i("test_language", "LocaleInfo "+lf);
        }

        //end
        return true;
    }

赋值的地方是mLocaleList = LocaleStore.getLevelLocales(context,
langTagsToIgnore, parent, translatedOnly); 继续往下追。

6.LocaleStore分析
主要看getLevelLocales这个函数

    /**
     * Returns a list of locales for language or region selection.
     * If the parent is null, then it is the language list.
     * If it is not null, then the list will contain all the locales that belong to that parent.
     * Example: if the parent is "ar", then the region list will contain all Arabic locales.
     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
     */
    public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
            LocaleInfo parent, boolean translatedOnly) {
        fillCache(context);
        String parentId = parent == null ? null : parent.getId();

        HashSet<LocaleInfo> result = new HashSet<>();
        for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
            int level = getLevel(ignorables, li, translatedOnly);
            if (level == 2) {
                if (parent != null) { // region selection
                    if (parentId.equals(li.getParent().toLanguageTag())) {
                        result.add(li);
                    }
                } else { // language selection
                    if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
                        result.add(li);
                    } else {
                        result.add(getLocaleInfo(li.getParent()));
                    }
                }
            }
        }
        return result;
    }

这里就是循环把sLocaleCache中的值读取出来,所以要去找这个sLocaleCache在哪赋值的。赋值的地方就是fillCache这个函数。

    public static void fillCache(Context context) {
        if (sFullyInitialized) {
            return;
        }

        Set<String> simCountries = getSimCountries(context);

        for (String localeId : LocalePicker.getSupportedLocales(context)) {
            if (localeId.isEmpty()) {
                throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
            }
            LocaleInfo li = new LocaleInfo(localeId);
            if (simCountries.contains(li.getLocale().getCountry())) {
                li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
            }
            sLocaleCache.put(li.getId(), li);
            final Locale parent = li.getParent();
            if (parent != null) {
                String parentId = parent.toLanguageTag();
                if (!sLocaleCache.containsKey(parentId)) {
                    sLocaleCache.put(parentId, new LocaleInfo(parent));
                }
            }
        }

        boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
        for (String localeId : LocalePicker.getPseudoLocales()) {
            LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
            if (isInDeveloperMode) {
                li.setTranslated(true);
                li.mIsPseudo = true;
                li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
            } else {
                sLocaleCache.remove(li.getId());
            }
        }

        // TODO: See if we can reuse what LocaleList.matchScore does
        final HashSet<String> localizedLocales = new HashSet<>();
        for (String localeId : LocalePicker.getSystemAssetLocales()) {
            LocaleInfo li = new LocaleInfo(localeId);
            final String country = li.getLocale().getCountry();
            // All this is to figure out if we should suggest a country
            if (!country.isEmpty()) {
                LocaleInfo cachedLocale = null;
                if (sLocaleCache.containsKey(li.getId())) { // the simple case, e.g. fr-CH
                    cachedLocale = sLocaleCache.get(li.getId());
                } else { // e.g. zh-TW localized, zh-Hant-TW in cache
                    final String langScriptCtry = li.getLangScriptKey() + "-" + country;
                    if (sLocaleCache.containsKey(langScriptCtry)) {
                        cachedLocale = sLocaleCache.get(langScriptCtry);
                    }
                }
                if (cachedLocale != null) {
                    cachedLocale.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CFG;
                }
            }
            localizedLocales.add(li.getLangScriptKey());
        }

        // Serbian in Latin script is only partially localized in N.
        localizedLocales.remove("sr-Latn");

        for (LocaleInfo li : sLocaleCache.values()) {
            li.setTranslated(localizedLocales.contains(li.getLangScriptKey()));
        }

        addSuggestedLocalesForRegion(Locale.getDefault());

        sFullyInitialized = true;
    }

代码很长,主要看LocalePicker.getSupportedLocales(context),它就是去配置文档中把语言列表读取出来的。继续往下追。

7.LocalePicker

    public static String[] getSupportedLocales(Context context) {
        return context.getResources().getStringArray(R.array.supported_locales);
    }

所以找到supported_locales这个数组就能找到加载的语言列表了。

8.supported_locales数组
位置在frameworks/base/core/res/res/values/locale_config.xml


<resources>

    <string-array translatable="false" name="supported_locales">
        <item>af-NA</item> <!-- Afrikaans (Namibia) -->
        <item>af-ZA</item> <!-- Afrikaans (South Africa) -->
        <item>agq-CM</item> <!-- Aghem (Cameroon) -->
        <item>ak-GH</item> <!-- Akan (Ghana) -->
        <item>am-ET</item> <!-- Amharic (Ethiopia) -->
        <item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
        <item>ar-BH</item> <!-- Arabic (Bahrain) -->
        <item>ar-DJ</item> <!-- Arabic (Djibouti) -->
        <item>ar-DZ</item> <!-- Arabic (Algeria) -->
        <item>ar-EG</item> <!-- Arabic (Egypt) -->
        <item>ar-EH</item> <!-- Arabic (Western Sahara) -->
        <item>ar-ER</item> <!-- Arabic (Eritrea) -->
        <item>ar-IL</item> <!-- Arabic (Israel) -->
        <item>ar-IQ</item> <!-- Arabic (Iraq) -->

在这里定制自己的语言列表,目前只测试过删除一些不需要的语言,没有添加新的。
注意:这里的语言有分类的,可以选择地区。比如上面代码中的Arabic语言,在设置中就显示这一个选项,当你点了这个Arabic选项后,就会出现Bahrain,Djibouti等这些选项。

展开阅读全文

没有更多推荐了,返回首页