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

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

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

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

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

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

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

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

View.generateViewId ()

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

Если вам это нужно для целей ниже уровня 17, вот его внутренняя реализация в View.java, которую вы можете использовать непосредственно в своем проекте, поместить в свой class 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()); } 

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

  res/values/ids.xml 

    

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

 myEditTextView.setId(R.id.my_edit_text_1); 

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

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

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

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

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

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

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

Конечно, статика здесь не нужна. Вы можете использовать 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: Только что я понял, что полностью сработал. Должно быть, я был пьян.

Просто добавление к ответу @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 * 

* 自动判断当前API Level,并优先调用{@link View#generateViewId()},即使本工具类与{@link View#generateViewId()} * 混用,也能保证生成的Id唯一*

* ============= *

* while {@link View#generateViewId()} require API Level >= 17, this tool is compatibe with all API. *

* according to current API Level, it decide weather using system API or not.
* 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(); } } }

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

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

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

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

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

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

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

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

generateViewId ()

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

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

Я использую:

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

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

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

Мой выбор:

 // Method that could us an unique id int getUniqueId(){ return (int) SystemClock.currentThreadTimeMillis(); } 
  • Google firebase проверяет, существует ли ребенок
  • Приложение неверно настроено для входа в Facebook: проблема интеграции Android Facebook
  • Замена fragmentа другим fragmentом внутри группы действий
  • Android: Как отслеживать происхождение InflateException?
  • После обновления Android-студия не смогла создать родительский каталог для файла блокировки
  • Как изменить имя приложения Android?
  • Gradle - Что такое ненулевое значение выхода и как его исправить?
  • Как создать диалоги datePicker и timePicker в classе fragmentов?
  • Нити AsyncTask никогда не умирают
  • Режим запуска «single top» для Android и onNewIntent
  • Как передать ArrayList объектов из одного в другое с использованием Intent в android?
  • Давайте будем гением компьютера.