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.