Android: View.setID (int id) програмно - як уникнути конфліктів ідентифікаторів?


334

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

Як я можу використовувати TextView.setId(int id)? Який ідентифікатор Integer мені придумати, щоб він не суперечив іншим ідентифікаторам?

Відповіді:


146

Відповідно до Viewдокументації

Ідентифікатор не повинен бути унікальним у ієрархії цього перегляду. Ідентифікатор повинен бути додатним числом.

Таким чином, ви можете використовувати будь-яке додатне ціле число, яке вам подобається, але в цьому випадку можуть бути деякі перегляди з еквівалентними ідентифікаторами. Якщо ви хочете шукати певний вигляд в ієрархії, виклик до setTagдеяких ключових об'єктів може бути корисним.


2
Цікаво, я не знав, що посвідчення особи не повинні бути унікальними? Тож чи findViewByIdдають будь-які гарантії щодо того, який вид повертається, якщо є більше одного з тим самим ідентифікатором? Документи нічого не згадують.
Маттіас

26
Я думаю, що документи згадують щось про це. Якщо у вас є перегляди з тим самим ідентифікатором в одній ієрархії, то findViewByIdповернете перше, що знайде.
kaneda

2
@DanyY Я не зовсім впевнений, чи правильно я розумію, що ти маєш на увазі. Що я намагався сказати, це те, що якщо макет, який ви встановили, setContentView()має, скажімо, 10 переглядів із встановленим ідентичним номером на той самий ідентифікаційний номер у тій же ієрархії , то виклик findViewById([repeated_id])поверне перший набір перегляду з цим повтореним ідентифікатором. Це я мав на увазі.
kaneda

51
-1 Я не згоден з цією відповіддю, тому що onSaveInstanceState і onRestoreInstanceState потрібен унікальний ідентифікатор, щоб можна було зберегти / відновити стан ієрархії перегляду. Якщо два погляди мають однаковий ідентифікатор, стан одного з них буде втрачено. Тому, якщо ви не збережете стан перегляду, все, що має дублікати ідентифікаторів, не є хорошою ідеєю.
Емануель Моеклін

3
Id повинен бути унікальним . Починаючи з рівня 17 API, в класі View існує статичний метод, який генерує випадковий Id для використання його як ідентифікатора перегляду. Цей метод гарантує, що згенерований ідентифікатор не зіткнеться з будь-яким іншим ідентифікатором перегляду, який вже генерується інструментом aapt протягом часу збирання. developer.android.com/reference/android/view/…
Махмуд

576

З рівня 17 і вище API ви можете зателефонувати: View.generateViewId ()

Потім використовуйте View.setId (int) .

Якщо ваш додаток орієнтовано нижче рівня 17 API, використовуйте ViewCompat.generateViewId ()


2
Я вкладаю його у свій вихідний код, оскільки ми хочемо підтримувати нижчі рівні API. Це працює, але нескінченний цикл не є хорошою практикою.
SXC

5
@SimonXinCheng Нескінченні петлі - це звичайна модель, яка використовується в алгоритмах, що не блокують. Наприклад, подивіться на AtomicIntegerреалізацію методів.
Ідолон

7
Чудово працює! Одне зауваження: виходячи з моїх експериментів, ви повинні зателефонувати setId () перед тим, як додати представлення до існуючого макета, інакше OnClickListener не працюватиме належним чином.
Лука

4
Дякую, ви були б занадто маленькими, але ДЯКУЮ ВАМ Питання, що for(;;)я ніколи раніше не бачив. Як це називається?
Агресор

5
@Aggressor: це порожній цикл "for".
sid_09

143

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

 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, і тому, що мені потрібен ідентифікатор, який тести знають, не додаючи ще однієї змінної. Але додайте <resources>.
Фліп

62

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

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml

15
Тут же відповідь з таким підходом: stackoverflow.com/questions/3216294 / ...
Ixx

Для довідки я знайшов файл у: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Тейлор Едмістон,


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;
}

Ця вищезазначена функція повинна бути достатньою. Тому що, наскільки я можу сказати, андроїд-генеровані ідентифікатори є в мільярдах, тому це, мабуть, повернеться 1в перший раз і завжди буде досить швидким. Тому що насправді не буде пробиратися повз використані ідентифікатори, щоб знайти невикористаний. Однак цикл є , якщо він дійсно знайде використаний ідентифікатор.

Однак якщо ви все ще хочете, щоб прогрес був збережений між наступними зменшеннями вашої програми, і ви хочете уникати використання статичних. Ось версія 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

EDIT / FIX: Щойно зрозумів, що я цілком збільшив економію. Я, мабуть, був п’яний.


1
Це має бути головна відповідь. Прекрасне використання ключових слів ++ та порожніх висловлювань;)
Aaron Gillion

9

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

Просто переконайтеся, що використовується версія Compatбібліотеки, яка є27.1.0+

Наприклад, у свій build.gradleфайл помістіть:

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

Тоді ви можете просто використовувати клас generateViewId()із 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();
        }

    }
}

@kenyee фрагмент коду for (;;) { … }походить від вихідного коду Android.
fantouch

Я розумію, що всі згенеровані ідентифікатори займають числовий простір 0x01000000–0xffffffff, тому вам гарантовано не зіткнення, але я не можу згадати, де я це читав.
Ендрю Вільд

Як скинути ..generateViewId()
reegan29

@kenyee має точку, вона може зіткнутися з ідентифікаторами, створеними в класі View. Дивіться мою відповідь :)
Співав

else { return View.generateViewId(); }це буде нескінченним циклом для рівня api менше 17 пристроїв?
okarakose

3

Для динамічного генерування API перегляду форми Id скористайтеся API 17

createViewId ()

Що генерує значення, придатне для використання в setId(int). Це значення не зіткнеться зі значеннями ID, згенерованими під час збирання aapt for 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);
}

Будь ласка, додайте належну характеристику до своєї відповіді, щоб зрозуміліше для ваших відповідей майбутнім відвідувачам. Відповіді, що стосуються лише коду, нахмурені та можуть бути видалені під час оглядів. Дякую!
Луїс Крус

-1

Мій вибір:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.