Android: View.setID (int id) programmgesteuert - wie vermeide ich ID-Konflikte?


333

Ich füge TextViews programmgesteuert in einer for-Schleife hinzu und füge sie einer ArrayList hinzu.

Wie benutze ich TextView.setId(int id)? Welche Ganzzahl-ID habe ich, damit sie nicht mit anderen IDs in Konflikt steht?

Antworten:


146

Laut ViewDokumentation

Der Bezeichner muss in der Hierarchie dieser Ansicht nicht eindeutig sein. Die Kennung sollte eine positive Zahl sein.

Sie können also eine beliebige positive Ganzzahl verwenden. In diesem Fall kann es jedoch einige Ansichten mit entsprechenden IDs geben. Wenn Sie nach einer Ansicht in der Hierarchie suchen möchten, setTagkann das Aufrufen mit einigen Schlüsselobjekten hilfreich sein.


2
Interessant, ich war mir nicht bewusst, dass IDs nicht eindeutig sein müssen? Gibt es findViewByIddann irgendwelche Garantien dafür, welche Ansicht zurückgegeben wird, wenn es mehr als eine mit derselben ID gibt? Die Dokumente erwähnen nichts.
Matthias

26
Ich denke, die Dokumente erwähnen etwas dazu. Wenn Sie Ansichten mit derselben ID in derselben Hierarchie haben, findViewByIdwird die erste gefundene zurückgegeben.
Kaneda

2
@DanyY Ich bin mir nicht ganz sicher, ob ich richtig verstehe, was du meinst. Ich habe versucht zu sagen, dass, wenn das Layout, mit dem Sie festgelegt setContentView()haben, beispielsweise 10 Ansichten mit derselben ID auf dieselbe ID-Nummer in derselben Hierarchie festgelegt hat , ein Aufruf von findViewById([repeated_id])die erste Ansicht zurückgibt, die mit dieser einen wiederholten ID festgelegt wurde. Das ist es was ich meinte.
Kaneda

51
-1 Ich stimme dieser Antwort nicht zu, da onSaveInstanceState und onRestoreInstanceState eine eindeutige ID benötigen, um den Status der Ansichtshierarchie speichern / wiederherstellen zu können. Wenn zwei Ansichten dieselbe ID haben, geht der Status einer von ihnen verloren. Wenn Sie also nicht den Ansichtsstatus speichern, ist es keine gute Idee, nur doppelte IDs zu haben.
Emanuel Moecklin

3
Die ID sollte eindeutig sein . Ab API-Ebene 17 gibt es in der View-Klasse eine statische Methode, die eine zufällige ID generiert, um sie als Ansichts-ID zu verwenden. Diese Methode stellt sicher, dass die generierte ID nicht mit einer anderen Ansichts-ID kollidiert, die bereits während der Erstellungszeit vom aapt-Tool generiert wurde. developer.android.com/reference/android/view/…
Mahmoud

576

Ab API-Level 17 und höher können Sie Folgendes aufrufen: View.generateViewId ()

Verwenden Sie dann View.setId (int) .

Wenn Ihre App auf eine niedrigere Stufe als API-Stufe 17 ausgerichtet ist, verwenden Sie ViewCompat.generateViewId ()


2
Ich habe es in meinen Quellcode eingefügt, weil wir niedrigere API-Ebenen unterstützen möchten. Es funktioniert, aber die Endlosschleife ist keine gute Praxis.
SXC

5
@SimonXinCheng Endlosschleifen sind ein häufiges Muster, das in nicht blockierenden Algorithmen verwendet wird. Schauen Sie sich zum Beispiel die AtomicIntegerMethodenimplementierung an.
Idolon

7
Funktioniert super! Ein Hinweis: Basierend auf meinen Experimenten müssen Sie setId () aufrufen, bevor Sie die Ansicht zu einem vorhandenen Layout hinzufügen, da OnClickListener sonst nicht ordnungsgemäß funktioniert.
Luke

4
Danke wäre zu klein aber DANKE. Frage, was for(;;)habe ich noch nie gesehen? Wie heißt das?
Aggressor

5
@Aggressor: Es ist eine leere 'for'-Schleife.
Sid_09

142

Sie können IDs, die Sie später im R.idUnterricht verwenden, mithilfe einer XML-Ressourcendatei festlegen und das Android SDK während der Kompilierungszeit eindeutige Werte festlegen lassen.

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

So verwenden Sie es im Code:

myEditTextView.setId(R.id.my_edit_text_1);

20
Dies funktioniert nicht, wenn ich eine unbekannte Anzahl von Elementen habe, denen ich IDs zuweisen werde.
Mooing Duck

1
@MooingDuck Ich weiß, dass dies ein Jahr zu spät ist, aber wenn ich zur Laufzeit eindeutige IDs mit einer unbekannten Anzahl von Elementen zuweisen muss, verwende ich einfach "int currentId = 1000; whateverView.setId(currentId++);- Das erhöht die ID bei jeder currentId++Verwendung, stellt eine eindeutige ID sicher und ich kann die speichern IDs in meiner ArrayList für den späteren Zugriff.
Mike in SAT

3
@ MikeinSAT: Das garantiert nur, dass sie untereinander einzigartig sind. Das macht es nicht "so dass es nicht mit anderen IDs in Konflikt steht", was ein wesentlicher Teil der Frage ist.
Mooing Duck

1
Dies ist die beste Antwort, weil andere das Code-Analyse-Tool von Android Studio angepasst haben und ich eine ID benötige, die die Tests kennen, ohne eine weitere Variable hinzuzufügen. Aber füge hinzu <resources>.
Phlip

61

Auch können Sie festlegen , ids.xmlin res/values. Sie können ein genaues Beispiel im Beispielcode von Android sehen.

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

15
Hier ist auch eine Antwort mit diesem Ansatz: stackoverflow.com/questions/3216294/…
Ixx

Als Referenz fand ich die Datei in: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston

28

Da 17 - API, die Viewhat Klasse eine statische Methode generateViewId() das wird

Generieren Sie einen Wert, der für die Verwendung in setId (int) geeignet ist.


25

Das funktioniert bei mir:

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

Das ist etwas komplizierter, aber ich wette, es wird funktionieren. Die Verwendung globaler Variablen in einer Multithread-Umgebung wird sicherlich eines Tages fehlschlagen, insbesondere bei mehreren Kernen.
Maaartinus

3
Ist dies bei komplizierten Layouts nicht möglicherweise langsam?
Daniel Rodriguez

15
findViewById()ist eine langsame Operation. Der Ansatz funktioniert, jedoch auf Kosten der Leistung.
Kiril Aleksandrov

10

(Dies war ein Kommentar zu Dilettantes Antwort, aber es wurde zu lang ... hehe)

Natürlich wird hier keine Statik benötigt. Sie können SharedPreferences zum Speichern anstelle von statischen verwenden. In beiden Fällen besteht der Grund darin, den aktuellen Fortschritt so zu speichern, dass er für komplizierte Layouts nicht zu langsam ist. Denn in der Tat wird es nach seiner einmaligen Verwendung später ziemlich schnell sein. Ich glaube jedoch nicht, dass dies ein guter Weg ist, dies zu tun, denn wenn Sie Ihren Bildschirm erneut erstellen müssen (zonCreate erstellen müssen (z. wird er erneut aufgerufen), möchten Sie wahrscheinlich trotzdem von vorne beginnen, sodass keine statische Aufladung erforderlich ist. Machen Sie es daher einfach zu einer Instanzvariablen anstelle einer statischen.

Hier ist eine kleinere Version, die etwas schneller läuft und möglicherweise leichter zu lesen ist:

int fID = 0;

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

Diese obige Funktion sollte ausreichend sein. Weil, soweit ich das beurteilen kann, von Android generierte IDs in Milliardenhöhe liegen, wird dies wahrscheinlich 1das erste Mal zurückkehren und immer recht schnell sein. Weil es nicht an den verwendeten IDs vorbeischleift, um eine nicht verwendete zu finden. Die Schleife ist jedoch vorhanden, wenn tatsächlich eine verwendete ID gefunden wird.

Wenn Sie jedoch weiterhin möchten, dass der Fortschritt zwischen den nachfolgenden Neuerstellungen Ihrer App gespeichert wird, und die Verwendung von statischer Aufladung vermeiden möchten. Hier ist die SharedPreferences-Version:

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

Diese Antwort auf eine ähnliche Frage sollte Ihnen alles sagen, was Sie über IDs mit Android wissen müssen: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: Ich habe gerade festgestellt, dass ich den Save total vermasselt habe. Ich muss betrunken gewesen sein.


1
Dies sollte die beste Antwort sein.
Aaron Gillion

9

Die 'Compat'-Bibliothek unterstützt jetzt auch die generateViewId()Methode für API-Level vor 17.

Stellen Sie einfach sicher, dass Sie eine Version der CompatBibliothek verwenden27.1.0+

Fügen Sie beispielsweise in Ihre build.gradleDatei Folgendes ein:

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

Dann können Sie einfach die generateViewId()aus der ViewCompatKlasse anstelle der ViewKlasse wie folgt verwenden:

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

Viel Spaß beim Codieren!


6

Nur eine Ergänzung zur Antwort von @phantomlimb,

während View.generateViewId()API Level> = 17 erforderlich ist,
ist dieses Tool Compatibe mit allen API.

Je nach aktuellem API-Level wird
das Wetter mithilfe der System-API entschieden oder nicht.

So können Sie ViewIdGenerator.generateViewId()und View.generateViewId()in der gleichen Zeit verwenden und müssen sich keine Sorgen machen, dass Sie dieselbe ID erhalten

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 Das Code-Snippet for (;;) { … }stammt aus dem Android-Quellcode.
Fantouch

Meines Wissens nach belegen alle generierten IDs den Nummernraum 0x01000000–0xffffffff, sodass Sie garantiert keinen Konflikt haben, aber ich kann mich nicht erinnern, wo ich dies gelesen habe.
Andrew Wyld

Zurücksetzen ..generateViewId()
reegan29

@kenyee hat einen Punkt, es kann mit IDs kollidieren, die in der View-Klasse generiert wurden. Siehe meine Antwort :)
Gesungen

else { return View.generateViewId(); }Dies wird eine Endlosschleife für API-Level kleiner als 17 Geräte?
Okarakose

3

Verwenden Sie zum dynamischen Generieren der View ID Form API 17

generateViewId ()

Dadurch wird ein Wert generiert, der für die Verwendung in geeignet ist setId(int). Dieser Wert kollidiert nicht mit ID-Werten, die zur Erstellungszeit von aapt for generiert wurden 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

Ich benutze:

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

Durch die Verwendung einer Zufallszahl habe ich immer eine große Chance, die eindeutige ID im ersten Versuch zu erhalten.


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

Bitte fügen Sie Ihrer Antwort eine korrekte Beschreibung hinzu, die für zukünftige Besucher klarer ist, um den Wert Ihrer Antwort beurteilen zu können. Nur-Code-Antworten sind verpönt und können bei Überprüfungen gelöscht werden. Vielen Dank!
Luís Cruz

-1

Meine Wahl:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.