Android: View.setID (int id) программно - как избежать конфликтов ID?

335

Я добавляю TextViews программно в цикл for и добавляю их в ArrayList.

Как я могу использовать TextView.setId(int id)? Какой Integer ID мне нужен, чтобы он не конфликтовал с другими ID?

znq
источник

Ответы:

147

Согласно Viewдокументации

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

Таким образом, вы можете использовать любое положительное целое число, которое вам нравится, но в этом случае могут быть некоторые представления с эквивалентными идентификаторами. Если вы хотите найти какое-то представление в иерархии, setTagможет пригодиться обращение к некоторым ключевым объектам.

Николай Иванов
источник
2
Интересно, я не знал, что идентификаторы не должны быть уникальными? Так что же findViewByIdдает гарантии относительно того, какое представление возвращается, если существует более одного с одним и тем же идентификатором? В документах ничего не упоминается.
Матиас
26
Я думаю, что документы упоминают что-то об этом. Если у вас есть представления с одинаковым идентификатором в одной и той же иерархии, то findViewByIdвернет первое, что он найдет.
Канеда
2
@DanyY Я не совсем уверен, правильно ли я понимаю, что вы имеете в виду. Я попытался сказать, что если в макете, который вы установили setContentView(), скажем, 10 видов с идентификатором, установленным на один и тот же номер идентификатора в одной и той же иерархии , то при вызове findViewById([repeated_id])будет возвращен первый набор с этим повторяющимся идентификатором. Это то, что я имел в виду.
Канеда
51
-1 Я не согласен с этим ответом, потому что onSaveInstanceState и onRestoreInstanceState нуждаются в уникальном идентификаторе, чтобы иметь возможность сохранять / восстанавливать состояние иерархии представлений. Если два представления имеют одинаковый идентификатор, состояние одного из них будет потеряно. Так что, если вы не сохраните состояние просмотра, все с дублирующимися идентификаторами сами по себе не будут хорошей идеей.
Эмануэль Меклин,
3
Id должен быть уникальным . Начиная с уровня API 17, в классе View есть статический метод, который генерирует случайный Id, чтобы использовать его в качестве идентификатора представления. Этот метод гарантирует, что сгенерированный идентификатор не будет конфликтовать с любым другим идентификатором представления, уже созданным инструментом aapt во время сборки. developer.android.com/reference/android/view/...
Махмуд
578

Начиная с уровня API 17 и выше, вы можете вызывать: View.generateViewId ()

Затем используйте View.setId (int) .

Если ваше приложение нацелено ниже уровня API 17, используйте ViewCompat.generateViewId ()

XY
источник
2
Я поместил его в свой исходный код, потому что мы хотим поддерживать более низкие уровни API. Это работает, но бесконечный цикл не является хорошей практикой.
SXC
5
@SimonXinCheng Бесконечные циклы - это распространенный шаблон, используемый в неблокирующих алгоритмах. Например, посмотрите на AtomicIntegerреализацию методов.
Идолон
7
Прекрасно работает! Одно замечание: основываясь на моих экспериментах, вы должны вызывать setId (), ПРЕЖДЕ ЧЕМ вы добавите представление в существующий макет, иначе OnClickListener не будет работать должным образом.
Лука
4
Спасибо было бы слишком мало, но СПАСИБО. Вопрос, что for(;;)я такого никогда раньше не видел. Как это называется?
Агрессор
5
@Aggressor: это пустой цикл for.
sid_09
143

Вы можете установить идентификаторы, которые будете использовать позже в R.idклассе, используя файл ресурсов xml, и позволить Android SDK давать им уникальные значения во время компиляции.

 res/values/ids.xml

<item name="my_edit_text_1" type="id"/>
<item name="my_button_1" type="id"/>
<item name="my_time_picker_1" type="id"/>

Чтобы использовать его в коде:

myEditTextView.setId(R.id.my_edit_text_1);
Сай Адитья
источник
20
Это не работает, когда у меня есть неизвестное количество элементов, которым я буду назначать идентификаторы.
Mooing Duck
1
@MooingDuck Я знаю, что это год с опозданием, но когда мне нужно назначить уникальные идентификаторы во время выполнения с неизвестным количеством элементов, я просто использую "int currentId = 1000; whateverView.setId(currentId++);- Это увеличивает идентификатор каждый раз, когда currentId++используется, обеспечивая уникальный идентификатор, и я могу сохранить Идентификаторы в моем ArrayList для последующего доступа.
Майк в субботу
3
@MikeinSAT: Это только гарантирует, что они уникальны между собой. Это не делает его «таким образом, это не вступает в конфликт с другими идентификаторами», что является ключевой частью вопроса.
Mooing Duck
1
Это победный ответ, потому что другие дали инструмент анализа кода Android Studio s-fit, и потому что мне нужен идентификатор, который тестирует без добавления еще одной переменной. Но добавь <resources>.
Флип
62

Также вы можете определить ids.xmlв res/values. Точный пример вы можете увидеть в примере кода Android.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
yenliangl
источник
15
Вот также ответ с этим подходом: stackoverflow.com/questions/3216294/…
Ixx
Для справки я нашел файл в: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Тейлор Эдмистон
28

Начиная с API 17, Viewкласс имеет статический метод, generateViewId() который будет

генерировать значение, подходящее для использования в setId (int)

Diederik
источник
25

Это работает для меня:

static int id = 1;

// Returns a valid id that isn't in use
public int findId(){  
    View v = findViewById(id);  
    while (v != null){  
        v = findViewById(++id);  
    }  
    return id++;  
}
дилетант
источник
Это немного сложнее, но я уверен, что это сработает. Использование глобальных переменных в многопоточной среде, безусловно, однажды потерпит неудачу, особенно с несколькими ядрами.
Maaartinus
3
Кроме того, разве это не медленно для сложных макетов?
Даниэль Родригес
15
findViewById()это медленная операция. Подход работает, но за счет производительности.
Кирилл Александров
10

(Это был комментарий к ответу дилетанта, но он стал слишком длинным ... хе-хе)

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

Вот уменьшенная версия, которая работает немного быстрее и может быть легче для чтения:

int fID = 0;

public int findUnusedId() {
    while( findViewById(++fID) != null );
    return fID;
}

Эта функция должна быть достаточной. Потому что, насколько я могу судить, сгенерированные Android идентификаторы исчисляются миллиардами, так что это, вероятно, вернется 1в первый раз и всегда будет довольно быстрым. Потому что на самом деле он не будет проходить мимо используемых идентификаторов, чтобы найти неиспользуемый. Однако, цикл является там действительно должно найти подержанный ID.

Однако, если вы все еще хотите сохранить прогресс между последующими воссозданиями вашего приложения и хотите избежать использования статического. Вот версия SharedPreferences:

SharedPreferences sp = getSharedPreferences("your_pref_name", MODE_PRIVATE);

public int findUnusedId() {
    int fID = sp.getInt("find_unused_id", 0);
    while( findViewById(++fID) != null );
    SharedPreferences.Editor spe = sp.edit();
    spe.putInt("find_unused_id", fID);
    spe.commit();
    return fID;
}

Этот ответ на аналогичный вопрос должен рассказать вам все, что вам нужно знать об идентификаторах с Android: https://stackoverflow.com/a/13241629/693927

РЕДАКТИРОВАТЬ / ИСПРАВИТЬ: Просто понял, что я полностью облажался. Должно быть, я был пьян.

Тачка Тризкит
источник
1
Это должен быть главный ответ. Отличное использование ключевого слова ++ и пустых выражений;)
Aaron Gillion
9

Библиотека Compat теперь также поддерживает generateViewId()метод уровней API до 17.

Просто убедитесь, что вы используете версию Compatбиблиотеки, которая27.1.0+

Например, в вашем build.gradleфайле введите :

implementation 'com.android.support:appcompat-v7:27.1.1

Тогда вы можете просто использовать класс generateViewId()from ViewCompatвместо Viewкласса следующим образом:

//Will assign a unique ID myView.id = ViewCompat.generateViewId()

Удачного кодирования!

Алекс Руссьер
источник
6

Просто дополнение к ответу @phantomlimb,

в то время как View.generateViewId()требуется уровень API> = 17,
этот инструмент совместим со всеми API.

в соответствии с текущим уровнем API,
он решает погоду, используя системный API или нет.

так что вы можете использовать ViewIdGenerator.generateViewId()и View.generateViewId()в то же время и не беспокоиться о получении того же идентификатора

import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.SuppressLint;
import android.os.Build;
import android.view.View;

/**
 * {@link View#generateViewId()}要求API Level >= 17,而本工具类可兼容所有API Level
 * <p>
 * 自动判断当前API Level,并优先调用{@link View#generateViewId()},即使本工具类与{@link View#generateViewId()}
 * 混用,也能保证生成的Id唯一
 * <p>
 * =============
 * <p>
 * while {@link View#generateViewId()} require API Level >= 17, this tool is compatibe with all API.
 * <p>
 * according to current API Level, it decide weather using system API or not.<br>
 * so you can use {@link ViewIdGenerator#generateViewId()} and {@link View#generateViewId()} in the
 * same time and don't worry about getting same id
 * 
 * @author fantouchx@gmail.com
 */
public class ViewIdGenerator {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    @SuppressLint("NewApi")
    public static int generateViewId() {

        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1; // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }

    }
}
fantouch
источник
@kenyee фрагмент кода взят for (;;) { … }из исходного кода Android.
fantouch
Насколько я понимаю, все сгенерированные идентификаторы занимают пространство 0x01000000–0xffffffff, так что вы гарантированно не столкнетесь, но я не могу вспомнить, где я это читал.
Эндрю Уайлд,
Как сбросить ..generateViewId()
reegan29
У @kenyee есть точка, она может конфликтовать с идентификаторами, сгенерированными в классе View. Смотрите мой ответ :)
Singed
else { return View.generateViewId(); }это будет бесконечный цикл для уровня API меньше 17 устройств?
Okarakose
3

Для динамической генерации API формы View Id используйте 17

generateViewId ()

Который будет генерировать значение, пригодное для использования в setId(int). Это значение не будет конфликтовать со значениями идентификатора, сгенерированными во время сборки aapt для R.id.

Арун С
источник
2
int fID;
do {
    fID = Tools.generateViewId();
} while (findViewById(fID) != null);
view.setId(fID);

...

public class Tools {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    public static int generateViewId() {
        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1; // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }
    }
}
Дмитрий
источник
1

Я использую:

public synchronized int generateViewId() {
    Random rand = new Random();
    int id;
    while (findViewById(id = rand.nextInt(Integer.MAX_VALUE) + 1) != null);
    return id;
}

Используя случайное число, у меня всегда есть огромный шанс получить уникальный идентификатор с первой попытки.

Бьёрн Стенфельдт
источник
0
public String TAG() {
    return this.getClass().getSimpleName();
}

private AtomicInteger lastFldId = null;

public int generateViewId(){

    if(lastFldId == null) {
        int maxFld = 0;
        String fldName = "";
        Field[] flds = R.id.class.getDeclaredFields();
        R.id inst = new R.id();

        for (int i = 0; i < flds.length; i++) {
            Field fld = flds[i];

            try {
                int value = fld.getInt(inst);

                if (value > maxFld) {
                    maxFld = value;
                    fldName = fld.getName();
                }
            } catch (IllegalAccessException e) {
                Log.e(TAG(), "error getting value for \'"+ fld.getName() + "\' " + e.toString());
            }
        }
        Log.d(TAG(), "maxId="+maxFld +"  name="+fldName);
        lastFldId = new AtomicInteger(maxFld);
    }

    return lastFldId.addAndGet(1);
}
chinwo
источник
Пожалуйста, добавьте правильное описание к вашему ответу так, чтобы будущие посетители могли оценить его ценность. Ответы только по коду не одобряются и могут быть удалены во время проверок. Спасибо!
Луис Крус
-1

Мой выбор:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
nimi0112
источник