Android: View.setID (int id) a livello di codice - come evitare conflitti di ID?


335

Sto aggiungendo TextViews a livello di codice in un ciclo continuo e li aggiungo a una ArrayList.

Come si usa TextView.setId(int id)? Quale ID intero viene visualizzato in modo che non sia in conflitto con altri ID?

Risposte:


147

Secondo la Viewdocumentazione

L'identificatore non deve essere univoco nella gerarchia di questa vista. L'identificatore dovrebbe essere un numero positivo.

Quindi puoi usare qualsiasi numero intero positivo che ti piace, ma in questo caso possono esserci alcune viste con ID equivalenti. Se vuoi cercare una vista nella gerarchia setTag, può essere utile chiamare con alcuni oggetti chiave.


2
Interessante, non sapevo che gli ID non dovessero essere univoci? Quindi, quindi, fornisce findViewByIdgaranzie su quale vista viene restituita se ce n'è più di una con lo stesso ID? I documenti non menzionano nulla.
Matthias,

26
Penso che i documenti menzionino qualcosa al riguardo. Se hai viste con lo stesso ID nella stessa gerarchia, findViewByIdverrà restituito il primo trovato.
Kaneda

2
@DanyY Non sono del tutto sicuro di capire correttamente cosa intendi. Quello che ho cercato di dire è che se il layout impostato setContentView()ha, diciamo, 10 viste con il loro ID impostato sullo stesso numero ID nella stessa gerarchia , quindi una chiamata a findViewById([repeated_id])restituirebbe la prima vista impostata con quell'ID ripetuto. Ecco cosa intendevo.
Kaneda,

51
-1 Non sono d'accordo con questa risposta perché onSaveInstanceState e onRestoreInstanceState necessitano di un ID univoco per poter salvare / ripristinare lo stato della gerarchia della vista. Se due viste hanno lo stesso ID, lo stato di una di esse andrà perso. Quindi, a meno che non salvi lo stato di visualizzazione, tutto te stesso con ID duplicati non è una buona idea.
Emanuel Moecklin,

3
L' ID dovrebbe essere unico . A partire dal livello API 17 esiste un metodo statico nella classe View che genera un ID casuale per usarlo come id view. Tale metodo garantisce che l'id generato non si scontrerà con nessun altro ID vista già generato dallo strumento aapt durante il tempo di compilazione. developer.android.com/reference/android/view/…
Mahmoud,

577

Dal livello API 17 in poi, puoi chiamare: View.generateViewId ()

Quindi utilizzare View.setId (int) .

Se la tua app ha un target inferiore al livello API 17, usa ViewCompat.generateViewId ()


2
L'ho inserito nel mio codice sorgente perché vogliamo supportare livelli API inferiori. Funziona ma il ciclo infinito non è una buona pratica.
SXC,

5
@SimonXinCheng I loop infiniti sono un modello comune utilizzato negli algoritmi non bloccanti. Ad esempio, dai un'occhiata AtomicIntegerall'implementazione dei metodi.
Idolon,

7
Funziona alla grande! Una nota: sulla base dei miei esperimenti, è necessario chiamare setId () PRIMA di aggiungere la vista a un layout esistente, altrimenti OnClickListener non funzionerà correttamente.
Luca,

4
Grazie sarebbe troppo piccolo ma GRAZIE. Domanda, cosa for(;;)non ho mai visto prima. Come si chiama?
Aggressor,

5
@Aggressor: è un ciclo 'for' vuoto.
sid_09

143

Puoi impostare gli ID che utilizzerai più avanti in R.idclasse utilizzando un file di risorse xml e lasciare che Android SDK fornisca loro valori univoci durante la compilazione.

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

Per usarlo nel codice:

myEditTextView.setId(R.id.my_edit_text_1);

20
Questo non funziona quando ho un numero sconosciuto di elementi a cui assegnerò gli ID.
Mooing Duck,

1
@MooingDuck So che è in ritardo di un anno, ma quando devo assegnare ID univoci in fase di esecuzione con un numero sconosciuto di elementi, uso semplicemente "int currentId = 1000; whateverView.setId(currentId++);- Ciò incrementa l'ID ogni volta che currentId++viene utilizzato, garantendo un ID univoco e posso archiviare il ID nella mia ArrayList per un accesso successivo.
Mike, sabato

3
@MikeinSAT: Questo garantisce solo che sono unici tra loro. Ciò non lo rende "quindi non è in conflitto con altri ID", che è una parte fondamentale della domanda.
Mooing Duck,

1
Questa è la risposta vincente perché altri stavano dando uno strumento di analisi del codice di Android Studio e perché ho bisogno di un ID che i test sappiano senza aggiungere ancora un'altra variabile. Ma aggiungi <resources>.
Phlip

62

Inoltre è possibile definire ids.xmlin res/values. Puoi vedere un esempio esatto nel codice di esempio di Android.

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

15
Ecco anche una risposta con questo approccio: stackoverflow.com/questions/3216294/…
Ixx

Per riferimento, ho trovato il file in: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston,

28

Dall'API 17, la Viewclasse ha un metodo statico generateViewId() che lo farà

generare un valore adatto per l'uso in setId (int)


25

Questo funziona per me:

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

Questo è un po 'più complicato, ma scommetto che funzionerà. L'uso di variabili globali in ambiente multithread fallirà sicuramente un giorno, specialmente con più core.
maaartinus,

3
Inoltre, non è forse lento per layout complicati?
Daniel Rodriguez,

15
findViewById()è un'operazione lenta. L'approccio funziona, ma a scapito delle prestazioni.
Kiril Aleksandrov,

10

(Questo è stato un commento alla risposta del dilettante, ma è passato troppo tempo ... hehe)

Ovviamente qui non è necessaria una statica. È possibile utilizzare SharedPreferences per salvare, anziché statico. In entrambi i casi, il motivo è quello di salvare i progressi attuali in modo che non sia troppo lento per layout complicati. Perché, in effetti, dopo essere stato usato una volta, sarà piuttosto veloce in seguito. Tuttavia, non credo che questo sia un buon modo per farlo perché se devi ricostruire nuovamente il tuo schermo (diciamo che onCreateviene chiamato di nuovo), allora probabilmente vorrai ricominciare dall'inizio dall'inizio, eliminando la necessità di elettricità statica. Pertanto, basta renderlo una variabile di istanza anziché statica.

Ecco una versione più piccola che funziona un po 'più veloce e potrebbe essere più facile da leggere:

int fID = 0;

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

Questa funzione sopra dovrebbe essere sufficiente. Perché, per quanto posso dire, gli ID generati da Android sono tra i miliardi, quindi probabilmente tornerà 1la prima volta e sarà sempre abbastanza veloce. Perché, in realtà non passerà attraverso gli ID usati per trovarne uno inutilizzato. Tuttavia, il ciclo è lì se dovesse effettivamente trovare un ID usato.

Tuttavia, se desideri comunque salvare i progressi tra le successive ricreazioni della tua app e vuoi evitare l'uso di elettricità statica. Ecco la versione di 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;
}

Questa risposta a una domanda simile dovrebbe dirti tutto ciò che devi sapere sugli ID con Android: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: ho appena realizzato che ho salvato parecchio il salvataggio. Devo essere stato ubriaco.


1
Questa dovrebbe essere la risposta migliore. Grande uso della parola chiave ++ e delle dichiarazioni vuote;)
Aaron Gillion

9

La libreria "Compat" ora supporta anche il generateViewId()metodo per i livelli API precedenti al 17.

Assicurati di usare una versione della Compatlibreria che è27.1.0+

Ad esempio, nel tuo build.gradlefile, inserisci:

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

Quindi puoi semplicemente usare il generateViewId()dalla ViewCompatclasse invece della Viewclasse come segue:

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

Buona programmazione!


6

Solo un'aggiunta alla risposta di @phantomlimb,

mentre View.generateViewId()richiede Livello API> = 17,
questo strumento è compatibile con tutte le API.

in base all'attuale livello API,
decide se utilizzare o meno l'API di sistema.

così puoi usare ViewIdGenerator.generateViewId()e View.generateViewId()allo stesso tempo e non preoccuparti di ottenere lo stesso 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();
        }

    }
}

@kenyee lo snippet di codice for (;;) { … }proviene dal codice sorgente di Android.
fantouch,

La mia comprensione è che tutti gli ID generati occupano lo spazio numerico 0x01000000-0ffffffff, quindi ti viene garantito un non-scontro, ma non ricordo dove ho letto questo.
Andrew Wyld,

Come resettare ..generateViewId()
reegan29

@kenyee ha un punto, può scontrarsi con gli ID generati all'interno della classe View. Vedi la mia risposta :)
Cantato il

else { return View.generateViewId(); }questo andrà in loop infinito per un livello API inferiore a 17 dispositivi?
Okarakose,

3

Al fine di generare dinamicamente l'utilizzo dell'ID di visualizzazione ID modulo 17

generateViewId ()

Che genererà un valore adatto per l'uso in setId(int). Questo valore non si scontrerà con i valori ID generati al momento della creazione da 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

Io uso:

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

Usando un numero casuale ho sempre un'enorme possibilità di ottenere l'ID univoco al primo tentativo.


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

Aggiungi una descrizione adeguata alla tua risposta in modo che sia più chiaro ai futuri visitatori per valutare il valore della tua risposta. Le risposte solo al codice sono disapprovate e possono essere eliminate durante le recensioni. Grazie!
Luís Cruz,

-1

La mia scelta:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.