Android: View.setID (int id) programáticamente: ¿cómo evitar conflictos de ID?

335

Estoy agregando TextViews programáticamente en un ciclo for y los agrego a una ArrayList.

¿Cómo lo uso TextView.setId(int id)? ¿Qué ID de entero se me ocurre para que no entre en conflicto con otras ID?

znq
fuente

Respuestas:

147

De acuerdo con la Viewdocumentación

El identificador no tiene que ser único en la jerarquía de esta vista. El identificador debe ser un número positivo.

Por lo tanto, puede usar cualquier número entero positivo que desee, pero en este caso puede haber algunas vistas con id equivalentes. Si desea buscar alguna vista en la jerarquía, llamar setTagcon algunos objetos clave puede ser útil.

Nikolay Ivanov
fuente
2
Interesante, ¿no sabía que las identificaciones no tienen que ser únicas? Entonces, ¿ofrece findViewByIdalguna garantía sobre qué vista se devuelve si hay más de una con la misma ID? Los documentos no mencionan nada.
Matthias
26
Creo que los documentos mencionan algo sobre esto. Si tiene vistas con la misma ID en la misma jerarquía findViewById, devolverá la primera que encuentre.
kaneda
2
@DanyY No estoy muy seguro si entiendo correctamente lo que quieres decir. Lo que intenté decir fue que si el diseño con el que configuró setContentView()tiene, digamos, 10 vistas con su ID establecido en el mismo número de identificación en la misma jerarquía , entonces una llamada a findViewById([repeated_id])devolvería la primera vista establecida con esa identificación repetida. A eso me refería.
kaneda
51
-1 No estoy de acuerdo con esta respuesta porque onSaveInstanceState y onRestoreInstanceState necesitan una identificación única para poder guardar / restaurar el estado de la jerarquía de vistas. Si dos vistas tienen la misma identificación, se perderá el estado de una de ellas. Por lo tanto, a menos que guarde el estado de Vista, no es buena idea tener identificadores duplicados.
Emanuel Moecklin
3
La identificación debe ser única . A partir del nivel 17 de API, hay un método estático en la clase View que genera un Id. Aleatorio para usarlo como id. Ese método asegura que la identificación generada no colisionará con ninguna otra identificación de vista ya generada por la herramienta aapt durante el tiempo de construcción. developer.android.com/reference/android/view/…
Mahmoud
577

Desde el nivel API 17 y superior, puede llamar a: View.generateViewId ()

Luego use View.setId (int) .

Si su aplicación está dirigida a un nivel inferior al API 17, use ViewCompat.generateViewId ()

XY
fuente
2
Lo puse en mi código fuente porque queremos admitir niveles de API más bajos. Está funcionando pero el bucle infinito no es una buena práctica.
SXC
55
@SimonXinCheng Infinite loops es un patrón común utilizado en algoritmos sin bloqueo. Por ejemplo, eche un vistazo a la AtomicIntegerimplementación de métodos.
Idolon
77
¡Funciona genial! Una nota: según mis experimentos, debe llamar a setId () ANTES de agregar la vista a un diseño existente, de lo contrario, OnClickListener no funcionará correctamente.
Lucas
44
Gracias sería demasiado pequeño pero GRACIAS. Pregunta, ¿qué es for(;;)lo que nunca había visto antes? ¿Cómo se llama eso?
Agresor
55
@Aggressor: es un bucle vacío 'for'.
sid_09
143

Puede establecer las ID que usará más adelante en la R.idclase utilizando un archivo de recursos xml y dejar que el SDK de Android les otorgue valores únicos durante el tiempo de compilación.

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

Para usarlo en el código:

myEditTextView.setId(R.id.my_edit_text_1);
Sai Aditya
fuente
20
Esto no funciona cuando tengo una cantidad desconocida de elementos a los que les voy a asignar identificadores.
Mooing Duck
1
@MooingDuck Sé que esto lleva un año de retraso, pero cuando tengo que asignar ID únicos en tiempo de ejecución con un número desconocido de elementos, simplemente uso "int currentId = 1000; whateverView.setId(currentId++);: eso incrementa la ID cada vez que currentId++se usa, asegurando una ID única, y puedo almacenar el ID en mi ArrayList para acceso posterior.
Mike en SAT
3
@MikeinSAT: Eso solo garantiza que son únicos entre ellos. Eso no lo hace "por lo que no entra en conflicto con otras identificaciones", que es una parte clave de la pregunta.
Mooing Duck
1
Esta es la respuesta ganadora porque otros le estaban dando un buen ajuste a la herramienta de análisis de código de Android Studio, y porque necesito una identificación que pruebe sin agregar otra variable. Pero agrega <resources>.
Phlip
62

También puedes definirlo ids.xmlen res/values. Puede ver un ejemplo exacto en el código de muestra de Android.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
yenliangl
fuente
15
Aquí también hay una respuesta con este enfoque: stackoverflow.com/questions/3216294/…
Ixx
Como referencia, encontré el archivo en: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston
28

Desde API 17, la Viewclase tiene un método estático generateViewId() que

generar un valor adecuado para usar en setId (int)

Diederik
fuente
25

Esto funciona para mi:

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++;  
}
diletante
fuente
Esto es un poco más complicado, pero apuesto a que funcionará. El uso de variables globales en un entorno multiproceso seguramente fallará algún día, especialmente con múltiples núcleos.
maaartinus
3
Además, ¿no es esto posiblemente lento para diseños complicados?
Daniel Rodriguez
15
findViewById()Es una operación lenta. El enfoque funciona, pero a costa del rendimiento.
Kiril Aleksandrov
10

(Este fue un comentario a la respuesta del diletante pero se hizo demasiado largo ... jeje)

Por supuesto, aquí no se necesita una estática. Puede usar SharedPreferences para guardar, en lugar de static. De cualquier manera, la razón es guardar el progreso actual para que no sea demasiado lento para diseños complicados. Porque, de hecho, después de usarse una vez, será bastante rápido más tarde. Sin embargo, no creo que esta sea una buena manera de hacerlo porque si tiene que reconstruir su pantalla nuevamente (por ejemplo, onCreatese le llama nuevamente), entonces de todos modos probablemente quiera comenzar de nuevo, eliminando la necesidad de estática. Por lo tanto, simplemente conviértalo en una variable de instancia en lugar de estática.

Aquí hay una versión más pequeña que se ejecuta un poco más rápido y podría ser más fácil de leer:

int fID = 0;

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

Esta función anterior debería ser suficiente. Porque, por lo que puedo decir, las ID generadas por Android están en miles de millones, por lo que probablemente esto regrese 1la primera vez y siempre sea bastante rápido. Porque, en realidad, no estará pasando los ID utilizados para encontrar uno no utilizado. Sin embargo, el ciclo es allí si realmente encuentra una identificación usada.

Sin embargo, si aún desea que se guarde el progreso entre recreaciones posteriores de su aplicación, y desea evitar el uso de estática. Aquí está la versión de 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;
}

Esta respuesta a una pregunta similar debería decirle todo lo que necesita saber sobre ID con Android: https://stackoverflow.com/a/13241629/693927

EDITAR / ARREGLO: Acabo de darme cuenta de que me burlé por completo de guardar. Debo haber estado borracho.

Pimp Trizkit
fuente
1
Esta debería ser la mejor respuesta. Gran uso de palabras clave ++ y declaraciones vacías;)
Aaron Gillion
9

La biblioteca 'Compat' ahora también admite el generateViewId()método para niveles de API anteriores al 17.

Solo asegúrese de usar una versión de la Compatbiblioteca que sea27.1.0+

Por ejemplo, en su build.gradlearchivo, ponga:

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

Entonces puede simplemente usar el generateViewId()de la ViewCompatclase en lugar de la Viewclase de la siguiente manera:

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

¡Feliz codificación!

Alex Roussiere
fuente
6

Solo una adición a la respuesta de @phantomlimb,

mientras que View.generateViewId()requiere Nivel API> = 17,
esta herramienta es compatible con todas las API

según el nivel de API actual,
decide el clima usando la API del sistema o no.

para que pueda usar ViewIdGenerator.generateViewId()y View.generateViewId()al mismo tiempo y no se preocupe por obtener la misma identificación

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 [email protected]
 */
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();
        }

    }
}
fantouch
fuente
@kenyee el fragmento de código for (;;) { … }proviene del código fuente de Android.
fantouch
Según tengo entendido, todas las ID generadas ocupan el espacio numérico 0x01000000–0xffffffff, por lo que se garantiza un no conflicto, pero no recuerdo dónde leí esto.
Andrew Wyld
Cómo restablecer ..generateViewId()
reegan29
@kenyee tiene un punto, puede colisionar con los identificadores generados dentro de la clase View. Ver mi respuesta :)
Singed
else { return View.generateViewId(); }¿Esto irá a bucle infinito para un nivel de API menor a 17 dispositivos?
okarakose
3

Para generar dinámicamente el formulario de Id. De vista API 17, use

generateViewId ()

Lo que generará un valor adecuado para su uso en setId(int). Este valor no colisionará con los valores de ID generados en tiempo de compilación por aapt for R.id.

Arun C
fuente
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();
        }
    }
}
Dmitry
fuente
1

Yo suelo:

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

Al usar un número aleatorio siempre tengo una gran posibilidad de obtener la identificación única en el primer intento.

Bjørn Stenfeldt
fuente
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);
}
chinwo
fuente
Agregue una descripción adecuada a su respuesta de una manera que sea más clara para los futuros visitantes para evaluar el valor de su respuesta. Las respuestas de solo código están mal vistas y se pueden eliminar durante las revisiones. ¡Gracias!
Luís Cruz
-1

Mi elección:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
nimi0112
fuente