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

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

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

Ответ 1

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

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

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

Ответ 2

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

От уровня API 17 и выше вы можете позвонить

View.generateViewId()

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

Если вам нужно это для целей ниже уровня 17, вот его внутренняя реализация в View.java, которую вы можете использовать непосредственно в своем проекте, поместить в свой класс util или где-нибудь:

private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

/**
 * Generate a value suitable for use in {@link #setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    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;
        }
    }
}

Идентификационный номер, превышающий 0x00FFFFFF, зарезервирован для статических представлений, определенных в файлах /res xml. (Скорее всего, 0x7f ****** из R.java в моих проектах.)

В вашем коде вы можете:

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {

        myView.setId(Utils.generateViewId());

    } else {

        myView.setId(View.generateViewId());

    }

Ответ 3

Вы можете установить идентификатор, который вы будете использовать позже в классе 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);

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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: fooobar.com/questions/28127/...

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

Ответ 8

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

while View.generateViewId() требуется API Level >= 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 [email protected]
 */
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();
        }

    }
}

Ответ 9

Библиотека "Совместимость" теперь также поддерживает метод 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()

Счастливое кодирование!

Ответ 10

Чтобы динамически генерировать API-интерфейс формы представления, используйте

generateViewId()

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

Ответ 11

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

Ответ 12

Я использую:

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

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

Ответ 13

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

Ответ 14

Мой выбор:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }