Темы Зависимых Строк Android


Допустим, у меня есть приложение с 2 темами: мужской и женский. Темы просто меняют цветовую палитру и несколько рисунков, чтобы удовлетворить предпочтения пользователя.

Большое спасибо http://www.androidengineer.com/2010/06/using-themes-in-android-applications.html за его намеки на то, чтобы заставить это работать.

Но теперь давайте скажем, что я хочу получить немного симпатичнее с приложением и не только изменить цвета и чертежи, но я также хочу изменить строки. Например, я мог бы добавить пиратскую тему, а затем " Submit "будет" Arrrgh!"

Итак, мой основной вопрос: Как я могу изменить строки в своем приложении с помощью выбранных пользователем тем?

Правка:

Составляя это: приложение имеет 12 кнопок и 32 вида текста, которые я хотел бы иметь зависимыми от темы, и я хотел бы выполнить это без гигантского отображения или множества пользовательских attrs.

Все 3 текущих решения будут работать. Ищу что-нибудь почище, хотя я этого не делаю. знайте, что такой зверь существует.

5 6

5 ответов:

Предположим, у меня есть приложение с 2 темами: мужской и женский. Темы просто меняют цветовую палитру и несколько рисунков, чтобы удовлетворить предпочтения пользователя.
Как насчет того, чтобы сделать вид, что ты занимаешься чем-то другим? Это дизайн-анти-паттерн, ассоциирующий определенные цвета в зависимости от пола (например, "девушки любят розовый"). Это не значит, что ваша техническая цель плоха, просто это действительно стереотипный подход. образец.

Например, я могу добавить пиратскую тему, а затем " Submit "будет" Arrrgh!"

Только если" отмена " соответствует "Avast!".

Как я могу изменить строки во всем моем приложении с помощью выбранных пользователем тем?

Вы не сказали, Откуда берутся эти струны. Являются ли они струнными ресурсами? Записи в базе данных? Те, которые вы извлекаете из веб-службы? Что-то еще?

Я предположу на данный момент что это строковые ресурсы. По определению, вам нужно будет иметь N копий строк, по одной на тему.

Поскольку пол и пиратский статус не отслеживаются Android как возможные квалификаторы набора ресурсов, вы не можете иметь эти строковые ресурсы в разных наборах ресурсов. Хотя они могут находиться в разных файлах (например, res/values/strings_theme1.xml), имена файлов не являются частью идентификаторов ресурсов для строк. Таким образом, вам придется использовать какой-то префикс/суффикс, чтобы отслеживать, какой именно строки принадлежат к каким темам (например, @string/btn_submit_theme1).

Если эти строки не изменяются во время выполнения - это просто то, что находится в вашем ресурсе макета-вы можете взять страницу из библиотеки каллиграфии Криса Дженкинса . У него есть свой собственный подкласс LayoutInflater, используемый для перегрузки некоторых стандартных атрибутов XML. В его случае его внимание сосредоточено на android:fontFamily, где он поддерживает это сопоставление с файлом шрифта в assets.

В вашем случае вы можете перегрузить android:text. В вашем файле макета, скорее чем он указывает на любую из ваших реальных строк, вы можете иметь это базовое имя нужного вам строкового ресурса, без какого-либо идентификатора темы (например, если реальные строки @string/btn_submit_theme1 и kin, вы можете иметь android:text="btn_submit"). Ваш подкласс LayoutInflater захватит это значение, добавит суффикс имени темы, использует getIdentifier() на вашем Resources, чтобы найти фактический идентификатор ресурса строки, и оттуда получит строку, привязанную к вашей теме.

Одним из вариантов этого было бы поместить базовое имя в android:tag вместо android:text. android:text может указать на один из ваших реальных строковых ресурсов, чтобы помочь с дизайном графического интерфейса и тому подобное. Ваш LayoutInflater захватит тег и использует его для получения нужной строки во время выполнения.

Если вы будете заменять текст другим текстом, извлеченным из тематических строковых ресурсов,вы можете изолировать свою логику get-the-string-given-the-base-name в статический служебный метод, который вы можете применить.

В то время как получение этого права первоначально займет немного работы, оно будет масштабироваться до произвольного сложность, с точки зрения количества затронутых виджетов пользовательского интерфейса и строк. Вы все еще должны помнить, чтобы добавить значения для всех тем для любых новых строк, которые вы определяете (бонусные баллы для создания пользовательской проверки Lint или задачи Gradle для проверки этого).

Да, это можно сделать, и вот как: сначала вам нужно будет определить атрибут темы, например:

<attr name="myStringAttr" format="string|reference" />

Затем, в ваших темах, добавьте эту строку

<item name="myStringAttr">Yarrrrr!</item>

Или

<item name="myStringAttr">@string/yarrrrr</item>

Затем вы можете использовать этот атрибут в XML-файле, например (обратите внимание на ? вместо @).

<TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="?attr/myStringAttr" />

Или, из кода, вот так:

public CharSequence resolveMyStringAttr(Context context) 
{
    Theme theme = context.getTheme();
    TypedValue value = new TypedValue();

    if (!theme.resolveAttribute(R.attr.myStringAttr, value, true)) {
        return null;
    }

    return value.string;
}

Поскольку ресурс-это всего лишь int в сердце, вы можете хранить их во время выполнения и заменять их процедурно по мере их использования.


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="OK_NORMAL">Okay</string>
    <string name="OK_PIRATE">Yaaarrrr!</string>
    <string name="OK_NINJA">Hooooaaa!</string>
</resources>

public enum ThemeMode {
    PIRATE,
    NINJA,
    NORMAL;
}

public class MyThemeStrings {
    public static int OK_PIRATE = R.string.OK_PIRATE;
    public static int OK_NINJA = R.string.OK_NINJA;
    public static int OK_NORMAL = R.string.OK_NORMAL;
}

public setOkButtonText(ThemeMode themeMode) {
    // buttonOk is instantiated elsewhere
    switch (themeMode) {
        case PIRATE:
            buttonOk.setText(MyThemeStrings.OK_PIRATE);
            break;
        case NINJA:
            buttonOk.setText(MyThemeStrings.OK_NINJA);
            break; 
        default:
            Log.e(TAG, "Unhandled ThemeMode: " + themeMode.name());
            // no break here so it flows into the NORMAL base case as a default
        case NORMAL:
            buttonOk.setText(MyThemeStrings.OK_NORMAL);
            break;
    }
}

Хотя, написав все это, вероятно, есть лучший способ сделать все это через отдельные XML-файлы. Я займусь этим сейчас и напишу второе решение, если найду его.

Хорошо, у меня есть второй вариант, который на самом деле может быть проще поддерживать и поддерживать ваш код чище, хотя он может быть более ресурсоемким из-за загрузки массива для каждой строки. Я не сравнивал его, но предложу его в качестве другого выбора, но я не буду использовать его, если вы предлагаете слишком много вариантов темы.


public enum ThemeMode {
    NORMAL(0),
    PIRATE(1),
    NINJA(2);

    private int index;
    private ThemeMode(int index) {
        this.index = index;
    }

    public int getIndex() {
        return this.index;
    }
}

<resources>
    <!-- ALWAYS define strings in the correct order according to 
         the index values defined in the enum -->
    <string-array
        name="OK_ARRAY">
        <item>OK</item>
        <item>Yaarrrr!</item>
        <item>Hooaaaa!</item>
    </string-array>
    <string-array
        name="CANCEL_ARRAY">
        <item>Cancel</item>
        <item>Naarrrrr!</item>
        <item>Wha!</item>
    </string-array>
</resources>

public setButtonTexts(Context context, ThemeMode themeMode) {
    // buttons are instantiated elsewhere
    buttonOk.setText(context.getResources()
            .getStringArray(R.array.CANCEL_ARRAY)[themeMode.getIndex()]);
    buttonCancel.setText(context.getResources()
            .getStringArray(R.array.OK_ARRAY)[themeMode.getIndex()]);
}

Итак, у меня не было возможности проверить это, но из чтения файла на Locale похоже, что вы можете создать свое собственное местоположение.

Http://developer.android.com/reference/java/util/Locale.html

И помощь от другого stackoverflow

Программно задать Локаль

Небольшая комбинация приводит меня к:

Locale pirate = new Locale("Pirate");
Configuration config = new Configuration();
config.locale = pirate;
this.getActivity().getBaseContext().getResources()
   .updateConfiguration(config, 
    this.getActivity().getBaseContext().getResources().getDisplayMetrics());

Я действительно считаю, что это позволит вам иметь res / values-pirate / strings в качестве фактического допустимого ресурса, который будет использоваться, когда ты-пират. Любые строки или настройки, которые вы не переопределяете, будут затем возвращены к res / values/... по умолчанию, так что вы можете сделать это для любого количества тем, как вы хотите. Опять же предполагая, что это работает.