Android: View.setID(int id) программно - как избежать конфликтов ID?
Я добавляю TextViews программно в цикл for и добавляю их в ArrayList.
Как я использую TextView.setId(int id)
? Какой Integer ID мне нужен, чтобы он не конфликтовал с другими ID?
14 ответов
В соответствии с View
документация
Идентификатор не обязательно должен быть уникальным в иерархии этого представления. Идентификатор должен быть положительным числом.
Таким образом, вы можете использовать любое положительное целое число, которое вам нравится, но в этом случае могут быть некоторые представления с эквивалентными идентификаторами. Если вы хотите найти какое-то представление в иерархии, обращаясь к setTag
с некоторыми ключевыми объектами может быть удобно.
Начиная с уровня API 17 и выше, вы можете вызывать: View.generateViewId ()
Затем используйте View.setId (int).
Если ваше приложение нацелено ниже уровня API 17, используйте ViewCompat.generateViewId()
Вы можете установить идентификаторы, которые вы будете использовать позже в 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);
Также вы можете определить ids.xml
в res/values
, Точный пример вы можете увидеть в примере кода Android.
samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
Поскольку API 17, View
класс имеет статический метод generateViewId()
что будет
генерировать значение, подходящее для использования в setId(int)
Это работает для меня:
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++;
}
Библиотека 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()
Удачного кодирования!
(Это был комментарий к ответу дилетанта, но он стал слишком длинным... хе-хе)
Конечно, статика здесь не нужна. Вы можете использовать SharedPreferences для сохранения вместо статического. В любом случае, причина заключается в том, чтобы сохранить текущий прогресс, чтобы он не был слишком медленным для сложных макетов. Потому что, на самом деле, после того, как его использовали один раз, это будет довольно быстро позже. Тем не менее, я не думаю, что это хороший способ сделать это, потому что, если вам придется восстановить свой экран снова (скажем, onCreate
будет вызван снова), тогда вы, возможно, захотите начать все сначала, исключая необходимость использования статического электричества. Поэтому просто сделайте его переменной экземпляра вместо статической.
Вот уменьшенная версия, которая работает немного быстрее и может быть легче для чтения:
int fID = 0;
public int findUnusedId() {
while( findViewById(++fID) != null );
return fID;
}
Эта функция должна быть достаточной. Потому что, насколько я могу судить, сгенерированные Android идентификаторы исчисляются миллиардами, так что это, вероятно, вернет 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: /questions/11432320/kak-programmno-naznachit-identifikator-dlya-predstavleniya/11432339#11432339
РЕДАКТИРОВАТЬ / ИСПРАВИТЬ: Просто понял, что я полностью облажался. Должно быть, я был пьян.
Просто дополнение к ответу @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();
}
}
}
Для динамической генерации API формы View Id 17 используйте
Который будет генерировать значение, подходящее для использования в setId(int)
, Это значение не будет конфликтовать со значениями идентификатора, сгенерированными во время сборки aapt для R.id
,
Вдохновленный ответом @dilettante, вот мое решение как функция расширения в kotlin:
/* sets a valid id that isn't in use */
fun View.findAndSetFirstValidId() {
var i: Int
do {
i = Random.nextInt()
} while (findViewById<View>(i) != null)
id = i
}
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();
}