Android: View.setID (int id) programowo - jak uniknąć konfliktu ID?


334

Dodaję TextViews programowo w pętli for i dodaję je do ArrayList.

Jak korzystać TextView.setId(int id)? Jaki identyfikator całkowity mam wymyślić, aby nie kolidował z innymi identyfikatorami?

Odpowiedzi:


146

Zgodnie z Viewdokumentacją

Identyfikator nie musi być unikalny w hierarchii tego widoku. Identyfikator powinien być liczbą dodatnią.

Możesz więc użyć dowolnej dodatniej liczby całkowitej, którą lubisz, ale w tym przypadku mogą istnieć niektóre widoki o równoważnych identyfikatorach. Jeśli chcesz wyszukać jakiś widok w hierarchii, setTagprzydatne może być wywołanie z niektórymi kluczowymi obiektami.


2
Co ciekawe, nie wiedziałem, że identyfikatory nie muszą być unikalne? Czy zatem daje findViewByIdjakiekolwiek gwarancje, który widok zostanie zwrócony, jeśli istnieje więcej niż jeden z tym samym identyfikatorem? Dokumenty nic nie wspominają.
Matthias

26
Myślę, że doktorzy wspominają coś o tym. Jeśli masz widoki o tym samym identyfikatorze w tej samej hierarchii findViewById, zwróci pierwszą znalezioną.
kaneda

2
@DanyY Nie jestem pewien, czy dobrze rozumiem, co masz na myśli. Próbowałem powiedzieć, że jeśli układ, który ustawiłeś setContentView()ma powiedzmy, 10 widoków z ich identyfikatorem ustawionym na ten sam numer identyfikacyjny w tej samej hierarchii , wówczas wywołanie findViewById([repeated_id])zwróci pierwszy zestaw widoków z tym jednym powtarzającym się identyfikatorem. O to mi chodziło.
kaneda

51
-1 Nie zgadzam się z tą odpowiedzią, ponieważ onSaveInstanceState i onRestoreInstanceState potrzebują unikalnego identyfikatora, aby móc zapisać / przywrócić stan hierarchii widoku. Jeśli dwa widoki mają ten sam identyfikator, stan jednego z nich zostanie utracony. Więc jeśli nie zapiszesz stanu Widok, wszyscy posiadający zduplikowane identyfikatory nie są dobrym pomysłem.
Emanuel Moecklin

3
Id powinien być unikalny . Począwszy od poziomu API 17, istnieje klasa statyczna w klasie View, która generuje losowy identyfikator, aby użyć go jako identyfikatora widoku. Ta metoda zapewnia, że ​​wygenerowany identyfikator nie będzie kolidował z żadnym innym identyfikatorem widoku, który został już wygenerowany przez narzędzie aapt podczas kompilacji. developer.android.com/reference/android/view/…
Mahmoud,

576

Od poziomu API 17 i wyższego można wywoływać: View.generateViewId ()

Następnie użyj View.setId (int) .

Jeśli twoja aplikacja jest kierowana poniżej poziomu API 17, użyj ViewCompat.generateViewId ()


2
Umieszczam go w kodzie źródłowym, ponieważ chcemy obsługiwać niższe poziomy API. Działa, ale nieskończona pętla nie jest dobrą praktyką.
SXC

5
@SimonXinCheng Pętle nieskończone są częstym wzorem stosowanym w algorytmach nieblokujących. Na przykład spójrz na AtomicIntegerimplementację metod.
Idolon

7
Działa świetnie! Jedna uwaga: na podstawie moich eksperymentów musisz wywołać setId () ZANIM dodasz widok do istniejącego układu, inaczej OnClickListener nie będzie działał poprawnie.
Łukasz

4
Dziękuję, byłbyś za mały, ale DZIĘKUJĘ. Pytanie, czego for(;;)nigdy wcześniej nie widziałem. Jak to się nazywa?
Agresor

5
@Agresor: Jest to pusta pętla „za”.
sid_09

143

Możesz ustawić identyfikatory, których będziesz używać później w R.idklasie, używając pliku zasobów xml, i pozwól, aby zestaw SDK systemu Android nadał im unikalne wartości podczas kompilacji.

 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"/>

Aby użyć go w kodzie:

myEditTextView.setId(R.id.my_edit_text_1);

20
To nie działa, gdy mam nieznaną liczbę elementów, do których będę przypisywać identyfikatory.
Mooing Duck

1
@MooingDuck Wiem, że to spóźnienie o rok, ale kiedy muszę przypisać unikalne identyfikatory w czasie wykonywania z nieznaną liczbą elementów, po prostu używam "int currentId = 1000; whateverView.setId(currentId++);- to zwiększa identyfikator za każdym razem currentId++, zapewniając unikalny identyfikator, i mogę przechowywać Identyfikatory w mojej ArrayList do późniejszego dostępu.
Mike w

3
@MikeinSAT: To tylko gwarantuje, że są wyjątkowe. To nie oznacza, że ​​„nie ma konfliktu z innymi identyfikatorami”, co jest kluczową częścią pytania.
Mooing Duck,

1
To jest zwycięska odpowiedź, ponieważ inni dali narzędziu analizy kodu Android Studio pasowanie, a ponieważ potrzebuję identyfikatora, który znają testy bez dodawania kolejnej zmiennej. Ale dodaj <resources>.
Phlip

62

Możesz także zdefiniować ids.xmlw res/values. Dokładny przykład można zobaczyć w przykładowym kodzie Androida.

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

15
Oto także odpowiedź z tym podejściem: stackoverflow.com/questions/3216294/…
Ixx

Dla porównania znalazłem plik w: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston


25

To działa dla mnie:

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

To jest trochę bardziej skomplikowane, ale założę się, że zadziała. Używanie zmiennych globalnych w środowisku wielowątkowym z pewnością zawiedzie się pewnego dnia, szczególnie w przypadku wielu rdzeni.
maaartinus

3
Czy to nie jest powolne w przypadku skomplikowanych układów?
Daniel Rodriguez

15
findViewById()jest powolną operacją. Podejście to działa, ale kosztem wydajności.
Kiril Aleksandrov

10

(To był komentarz do odpowiedzi dilettante, ale stał się za długi ... hehe)

Oczywiście statyczny nie jest tutaj potrzebny. Możesz użyć SharedPreferences do zapisania, zamiast statycznego. Tak czy inaczej, powodem jest zapisanie bieżącego postępu, aby nie był zbyt wolny dla skomplikowanych układów. Ponieważ w rzeczywistości po jednorazowym użyciu będzie później dość szybki. Jednak nie uważam, że jest to dobry sposób, aby to zrobić, ponieważ jeśli musisz ponownie przebudować ekran (powiedzmy, że onCreatejest wywoływany ponownie), prawdopodobnie i tak chcesz zacząć od początku, eliminując potrzebę statycznego. Dlatego po prostu ustaw zmienną instancji zamiast statycznej.

Oto mniejsza wersja, która działa nieco szybciej i może być łatwiejsza do odczytania:

int fID = 0;

public int findUnusedId() {
    while( findViewById(++fID) != null );
    return fID;
}

Ta powyższa funkcja powinna wystarczyć. Ponieważ, o ile wiem, generowane przez Androida identyfikatory są w miliardach, więc prawdopodobnie wróci 1za pierwszym razem i zawsze będzie dość szybkie. Ponieważ tak naprawdę nie będzie pętli obok używanych identyfikatorów, aby znaleźć nieużywany. Jednak pętla to nie powinno to rzeczywiście znaleźć używany identyfikator.

Jeśli jednak nadal chcesz, aby postęp był zapisywany między kolejnymi odtworzeniami aplikacji i chcesz uniknąć korzystania ze statycznego. Oto wersja 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;
}

Ta odpowiedź na podobne pytanie powinna powiedzieć ci wszystko, co musisz wiedzieć o identyfikatorach w Androidzie: https://stackoverflow.com/a/13241629/693927

EDYCJA / POPRAWKA: Właśnie zdałem sobie sprawę, że całkowicie wygłupiłem rzut obronny. Musiałem być pijany.


1
To powinna być najlepsza odpowiedź. Świetne użycie słowa kluczowego ++ i pustych instrukcji;)
Aaron Gillion

9

Biblioteka „Compat” obsługuje teraz także generateViewId()metodę poziomów API wcześniejszych niż 17.

Upewnij się tylko, że używasz wersji Compatbiblioteki27.1.0+

Na przykład w build.gradlepliku umieść:

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

Następnie możesz po prostu użyć generateViewId()z ViewCompatklasy zamiast z Viewklasy w następujący sposób:

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

Miłego kodowania!


6

Tylko dodatek do odpowiedzi @phantomlimb,

natomiast View.generateViewId()wymagają API poziom> = 17,
to narzędzie jest compatibe ze wszystkimi API.

zgodnie z bieżącym poziomem interfejsu API
decyduje o pogodzie przy użyciu systemowego interfejsu API lub nie.

dzięki czemu można używać ViewIdGenerator.generateViewId()i View.generateViewId()w tym samym czasie i nie martw się o uzyskanie tego samego identyfikatora

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 fragment kodu for (;;) { … }pochodzi z kodu źródłowego Androida.
fantouch

Rozumiem, że wszystkie wygenerowane identyfikatory zajmują przestrzeń liczbową 0x01000000–0xffffffff, więc masz gwarancję bezkolizyjności, ale nie pamiętam, gdzie to przeczytałem.
Andrew Wyld

Jak zresetować ..generateViewId()
reegan29

@ kenyee ma rację, może kolidować z identyfikatorami generowanymi w klasie View. Zobacz moją odpowiedź :)
Singed

else { return View.generateViewId(); }czy będzie to nieskończona pętla dla poziomu interfejsu API mniejszego niż 17 urządzeń?
okarakose

3

W celu dynamicznego generowania Widoku formularza należy użyć API 17

generateViewId ()

Co wygeneruje wartość odpowiednią do użycia w setId(int). Ta wartość nie będzie kolidować z wartościami ID wygenerowanymi w czasie kompilacji przez 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

Używam:

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

Używając losowej liczby, zawsze mam ogromną szansę na uzyskanie unikalnego identyfikatora za pierwszym razem.


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

Dodaj odpowiedni opis do swojej odpowiedzi w sposób, który będzie bardziej zrozumiały dla przyszłych użytkowników, aby ocenić wartość Twojej odpowiedzi. Odpowiedź tylko na kod jest niezadowolona i można ją usunąć podczas recenzji. Dzięki!
Luís Cruz,

-1

Mój wybór:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.