Существует ли шаблон поиска строковых ресурсов Enum для Android?

У меня есть перечисление, в котором мне нужно отображать значения в виде локализованных строк. Мой текущий подход был следующим:

public enum MyEnum {
    VALUE1(R.string.VALUE1),
    VALUE2(R.string.VALUE2),
    .
    .
    VALUE10(R.string.VALUE10);

    private int mResId = -1;

    private MuEnum(int resId) {
        mResId = resId;
    }

    public String toLocalizedString(Resources r) {
        if (-1 != mResId) return (r.getString(mResId));
        return (this.toString());
    }
}

Есть ли более простой способ сделать это? Мне бы понравилось, если бы я мог каким-то образом найти ресурс на основе имени значения перечисления (например, «VALUE1»).

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="VALUE1"/>My string</string>
    <string name="VALUE2"/>My string 2</string>
    .
    .
    <string name="VALUE10"/>My string 3</string>
</resources>

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

РЕДАКТИРОВАТЬ: Просто для справки в будущем это решение, которое лучше всего сработало для меня:

public enum MyEnum {
    VALUE1, 
    VALUE2, 
    . 
    . 
    VALUE10; 

    /**
     * Returns a localized label used to represent this enumeration value.  If no label
     * has been defined, then this defaults to the result of {@link Enum#name()}.  
     * 
     * <p>The name of the string resource for the label must match the name of the enumeration
     * value.  For example, for enum value 'ENUM1' the resource would be defined as 'R.string.ENUM1'.
     * 
     * @param context   the context that the string resource of the label is in.
     * @return      a localized label for the enum value or the result of name()
     */
    public String getLabel(Context context) {
        Resources res = context.getResources();
        int resId = res.getIdentifier(this.name(), "string", context.getPackageName());
        if (0 != resId) {
            return (res.getString(resId));
        }
        return (name());
    }
}

person jsmith    schedule 16.03.2012    source источник
comment
Посмотрите этот пост, для этого вопроса есть гораздо более простое решение: stackoverflow.com/a/29625457/1891118   -  person Oleksii K.    schedule 14.04.2015


Ответы (6)


Вы, безусловно, можете найти ресурс по его имени, используя Resources.getIdentifier(). Например, со строковыми ресурсами, которые вы разместили в качестве примера, вы можете сделать это из действия:

Resources res = getResources();
MyEnum e = MyEnum.VALUE1;
String localized = res.getString(res.getIdentifier(e.name(), "string", getPackageName()));

Из представления вам нужно будет изменить последний аргумент на getContext().getPackageName()

person Ted Hopp    schedule 16.03.2012
comment
Есть ли какая-то причина, по которой вы не могли бы просто напрямую ввести имя пакета, в котором находится перечисление, вместо «getPackageName()»? Я спрашиваю, потому что мое перечисление находится в библиотеке, совместно используемой несколькими проектами. - person jsmith; 16.03.2012
comment
@jsmith - именно поэтому вы не хотели бы напрямую вводить имя пакета. Имя пакета, которое там указано, — это имя, объявленное в манифесте, а не пакет, в котором находится перечисление. - person Ted Hopp; 16.03.2012
comment
Я просмотрел сгенерированные пакеты в приложениях, использующих библиотеку, и, кажется, понял, что вы имеете в виду. Но мне бы хотелось объяснить, как работает упаковка с включенными библиотеками. Можете ли вы сослаться на документацию по этому вопросу? - person jsmith; 16.03.2012
comment
@jsmith Конечно. Подробнее о проектах библиотек читайте здесь. Помимо документации для файла AndroidManifest.xml, блог опубликовать Вещи, которые нельзя изменить стоит чтение. - person Ted Hopp; 16.03.2012
comment
Вместо getPackageName() вы также можете использовать BuildConfig.APPLICATION_ID и избавить себя от проблем с контекстом. - person Gerhard Burger; 30.12.2014
comment
@GerhardBurger - BuildConfig.APPLICATION_ID доступен только для сборок Gradle/Android Studio. Он не определен для других сборок. - person Ted Hopp; 30.12.2014
comment
Хорошо знать! Но Android Studio кажется подходящим вариантом для будущих проектов;) - person Gerhard Burger; 30.12.2014
comment
@GerhardBurger - Возможно, так. Я все еще использую ADT/Eclipse. Но я подозреваю, что BuildConfig.APPLICATION_ID неверен даже для сборок Gradle. Если рассматриваемый код является частью библиотеки, идентификатор приложения зависит от того, какой проект использует библиотеку, и неизвестен во время компиляции. Я подозреваю, что он будет использовать объявленное имя приложения библиотеки, а неправильное имя будет встроено в сгенерированный файл класса. - person Ted Hopp; 30.12.2014
comment
@SiddarthG - проверьте опечатки, включая регистр. Убедитесь, что ресурс, который вы пытаетесь найти, упакован в ваш apk. (Вы можете проверить имена полей в сгенерированном компилятором файле R.java для всех имен ресурсов, упакованных в ваше приложение.) - person Ted Hopp; 24.03.2020

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


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

Лучшее решение

import android.app.Application;

public enum MyEnum {

    VALUE1(R.string.VALUE1),
    VALUE2(R.string.VALUE2),
    .
    .
    VALUE10(R.string.VALUE10);

    private int resourceId;

    private MyEnum(int id)  {
        resourceId = id;
    }


    @Override
    public String toString() {

        return MyApplication.getApplicationContext().getString(resourceId);

    }

}

Тогда вызов MyEnum.VALUEx всегда будет давать переведенное значение перечисления, но будьте осторожны, это может не всегда быть тем, что вам нужно, например, у вас может быть такой необработанный запрос:

select * from Customer where userStatus = MyEnum.VALUEx.toString();

Это может сломать ваше приложение, если вы сохраняете значения перечисления как VALUE1, VALUE2... в db, поэтому не забудьте использовать этот MyEnum.VALUEx.name(), если вы не хотите использовать переведенное значение вашего MyEnum.

select * from Customer where userStatus = MyEnum.VALUEx.name();
person dsharew    schedule 08.06.2015
comment
Что такое мое приложение? Если я возьму свою активность запуска, которая расширяет AppCompatActivity, я получаю следующую ошибку: нестатический метод getApplicationCOntext() - person lidox; 12.09.2016

Использование статического приложения всегда является плохой практикой, потому что оно не только нарушает Instant Run, но и противоречит принципу разделения программирования, что затрудняет реализацию модуляризации. Не говоря уже о том, что Android фактически поддерживает несколько приложений в одном процессе.

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

enum Example {
    A(R.string.label_a),
    B(R.string.label_b);

    Example(@StringRes int label) { mLabel = label; }
    private @StringRes int mLabel;

    class Entry {
        private Context mContext;
        Entry(final Context context) { mContext = context; }
        @Override public String toString() { return mContext.getString(mLabel); }
    }
}

Затем создайте экземпляр или массив Example.Entry для представления локализованной версии исходного перечисления.

Example.A.new Entry(context);

Arrays.stream(Example.values()).map(item -> item.new Entry(context)).toArray(Example.Entry[]::new)
person Oasis Feng    schedule 28.11.2016

Если вы подклассифицируете свой класс приложения, вы можете использовать его как singleton (см. http://androidcookbook.com/Recipe.seam?recipeId=1218). После того, как вы получили экземпляр singleton, вы можете использовать его в toLocalizedString() для получения ресурса объект и избавиться от параметра:

 public String getString() {
    return YourApp.getInstance().getResources().getString(resId);
}

вуаля - теперь у вас чистый интерфейс.

person Konstantin Pribluda    schedule 16.03.2012
comment
Мое перечисление является частью библиотеки, используемой несколькими приложениями. Это стало бы утомительно. У меня нет проблем с передачей значения. - person jsmith; 16.03.2012
comment
Но ваши идентификаторы ресурсов не переносятся между приложениями. - person Konstantin Pribluda; 16.03.2012

Сначала создайте новый класс:

import android.app.Application;

public class MyApplication extends Application {

    private static MyApplication singleton;

    public static MyApplication getInstance(){
        return singleton;
    }
    @Override
    public void onCreate() {

        super.onCreate();
        singleton = this;
    }
}

Теперь добавьте ссылку на класс вашего приложения в AndroidManifest.xml:

<application ... android:name="com.yourPackageName.application.MyApplication  ">

Затем создайте перечисление. Пример перечисления для пола:

public enum Gender {
MALE(0, R.string.male),
FEMALE(1, R.string.female);

private Integer resourceId;
private Integer index;

private static final Map<Integer, Gender> lookupIndex = new HashMap<Integer, Gender>();
private static final Map<Integer, Gender> lookupResourceId = new HashMap<Integer, Gender>();
private static final Map<String, Gender> lookupTranslation = new HashMap<String, Gender>();

static {
    for (Gender g : values()) {
        lookupIndex.put(g.getIndex(), g);
        lookupResourceId.put(g.getResourceId(), g);
        lookupTranslation.put(g.toString(), g);
    }
}

private Gender(Integer index, Integer displayText) {
    this.resourceId = displayText;
    this.index = index;
}

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

public Integer getResourceId() {
    return this.resourceId;
}

public static Gender findByIndex(Integer index) {
    return lookupIndex.get(index);
}

public static Gender findByResourceId(Integer id) {
    return lookupResourceId.get(id);
}

public static Gender findByTranslationText(String text) {
    return lookupTranslation.get(text);
}

@Override
public String toString() {
    return MyApplication.getInstance().getResources().getString(this.resourceId);
}}

Теперь вы можете использовать запрошенный шаблон поиска:

// by index
Gender male = Gender.findByIndex(0);

// by translation
String femaleTranslated = context.getResources().getString(R.string.female);
Gender gender = Gender.findByTranslationText(femaleTranslated);

// by id
Gender gender = Gender.findByResourceId(R.string.female);

Особая благодарность Ахмету Юксектепе

person lidox    schedule 12.09.2016

person    schedule
comment
Хотя эта команда может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и/или как этот код отвечает на вопрос, повышает его долгосрочную ценность. - person LordWilmore; 13.05.2019