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


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

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

14 269

14 ответов:

по данным View документация

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

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

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

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

вид.generateViewId ()

затем использовать вид.setId (int).

в случае, если вам это нужно для целей ниже уровня 17, вот его внутренняя реализация в поле зрения.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 зарезервирован для статических представлений, определенных в xml-файлах /res. (Скорее всего 0x7f***** * от R.java в моих проектах.)

в коде, вы можете сделать:

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

        myView.setId(Utils.generateViewId());

    } else {

        myView.setId(View.generateViewId());

    }

вы можете установить ID, который вы будете использовать позже в 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 in 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,View класс статический методgenerateViewId() что будет

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

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

конечно статика здесь не нужна. Вы можете использовать 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: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: только что понял, что я полностью обманул сохранение. Должно быть, я был пьян.

библиотека 'Compat' теперь также поддерживает generateViewId() метод для уровней API до 17.

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

например, в вашем build.gradle file, put:

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

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

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

удачи в кодировании !

просто дополнение к ответу @phantomlimb,

пока View.generateViewId() требуется уровень API >= 17,
этот инструмент совместим со всеми API.

в соответствии с текущим уровнем API,
он решает погоду с помощью системного API или нет.

так что вы можете использовать ViewIdGenerator.generateViewId() и View.generateViewId() в в то же время и не беспокойтесь о получении того же id

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