Android: View.setID (int id) par programmation - comment éviter les conflits d'ID?

335

J'ajoute TextViews par programme dans une boucle for et je les ajoute à une liste de tableaux.

Comment est-ce que j'utilise TextView.setId(int id)? Quel ID entier dois-je trouver pour qu'il n'entre pas en conflit avec d'autres ID?

znq
la source

Réponses:

147

Selon la Viewdocumentation

L'identifiant n'a pas besoin d'être unique dans la hiérarchie de cette vue. L'identifiant doit être un nombre positif.

Vous pouvez donc utiliser n'importe quel entier positif que vous aimez, mais dans ce cas, il peut y avoir des vues avec des identifiants équivalents. Si vous souhaitez rechercher une vue dans la hiérarchie, appeler setTagavec certains objets clés peut être pratique.

Nikolay Ivanov
la source
2
Intéressant, je ne savais pas que les identifiants n'ont pas besoin d'être uniques? Alors, fait-on findViewByIddes garanties quant à la vue qui est retournée s'il y en a plusieurs avec le même ID? Les documents ne mentionnent rien.
Matthias
26
Je pense que les documents mentionnent quelque chose à ce sujet. Si vous avez des vues avec le même ID dans la même hiérarchie findViewById, le premier sera trouvé.
kaneda
2
@DanyY Je ne suis pas sûr de bien comprendre ce que vous voulez dire. Ce que j'ai essayé de dire, c'est que si la mise en page que vous définissez setContentView()a, disons, 10 vues avec leur identifiant défini sur le même numéro d'identification dans la même hiérarchie , alors un appel à findViewById([repeated_id])retournerait le premier ensemble de vues avec cet identifiant répété. C'est ce que je voulais dire.
kaneda
51
-1 Je ne suis pas d'accord avec cette réponse car onSaveInstanceState et onRestoreInstanceState ont besoin d'un identifiant unique pour pouvoir enregistrer / restaurer l'état de la hiérarchie des vues. Si deux vues ont le même identifiant, l'état de l'une d'entre elles sera perdu. Donc, sauf si vous enregistrez l'état d'affichage vous-même avec des identifiants en double, ce n'est pas une bonne idée.
Emanuel Moecklin
3
L' ID doit être unique . À partir du niveau 17 de l' API, il existe une méthode statique dans la classe View qui génère un ID aléatoire pour l'utiliser comme identifiant de vue. Cette méthode garantit que l'ID généré n'entrera pas en collision avec un autre ID de vue déjà généré par l'outil aapt pendant la construction. developer.android.com/reference/android/view/…
Mahmoud
578

Depuis l'API de niveau 17 et supérieur, vous pouvez appeler: View.generateViewId ()

Utilisez ensuite View.setId (int) .

Si votre application est ciblée à un niveau inférieur au niveau 17 de l'API, utilisez ViewCompat.generateViewId ()

XY
la source
2
Je l'ai mis dans mon code source parce que nous voulons prendre en charge des niveaux d'API inférieurs. Cela fonctionne mais la boucle infinie n'est pas une bonne pratique.
SXC
5
@SimonXinCheng Infinite loops est un modèle courant utilisé dans les algorithmes non bloquants. Par exemple, jetez un œil à l' AtomicIntegerimplémentation des méthodes.
Idolon
7
Fonctionne très bien! Une remarque: sur la base de mes expériences, vous devez appeler setId () AVANT d'ajouter la vue à une mise en page existante, sinon OnClickListener ne fonctionnera pas correctement.
Luke
4
Merci serait trop petit mais MERCI. Question, qu'est-ce que for(;;)je n'ai jamais vu ça auparavant. Comment ça s'appelle?
Agresseur
5
@Aggressor: C'est une boucle vide pour.
sid_09
143

Vous pouvez définir les identifiants que vous utiliserez plus tard en R.idclasse à l'aide d'un fichier de ressources xml et laisser le SDK Android leur donner des valeurs uniques lors de la compilation.

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

Pour l'utiliser dans le code:

myEditTextView.setId(R.id.my_edit_text_1);
Sai Aditya
la source
20
Cela ne fonctionne pas lorsque j'ai un nombre inconnu d'éléments auxquels j'attribuerai des identifiants.
Mooing Duck
1
@MooingDuck Je sais que c'est un an de retard, mais quand je dois attribuer des ID uniques au moment de l'exécution avec un nombre inconnu d'éléments, j'utilise simplement "int currentId = 1000; whateverView.setId(currentId++);- Cela incrémente l'ID à chaque currentId++utilisation, garantissant un ID unique, et je peux stocker le ID dans ma liste de tableaux pour un accès ultérieur.
Mike le samedi
3
@MikeinSAT: Cela garantit seulement qu'ils sont uniques entre eux. Cela ne signifie pas que "cela n'entre pas en conflit avec d'autres identifiants", ce qui est un élément clé de la question.
Mooing Duck
1
C'est la réponse gagnante parce que d'autres donnaient à l'outil d'analyse de code d'Android Studio un ajustement s --- et parce que j'ai besoin d'un ID qui teste la connaissance sans ajouter encore une autre variable. Mais ajoutez <resources>.
Phlip
62

Vous pouvez également définir ids.xmldans res/values. Vous pouvez voir un exemple exact dans l'exemple de code d'Android.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
yenliangl
la source
15
Voici également une réponse avec cette approche: stackoverflow.com/questions/3216294/…
Ixx
Pour référence, j'ai trouvé le fichier dans: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston
28

Depuis l'API 17, la Viewclasse a une méthode statique generateViewId() qui

générer une valeur adaptée à une utilisation dans setId (int)

Diederik
la source
25

Cela fonctionne pour moi:

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++;  
}
dilettante
la source
C'est un peu plus compliqué mais je parie que ça va marcher. L'utilisation de variables globales dans un environnement multithread échouera sûrement un jour, en particulier avec plusieurs cœurs.
maaartinus
3
De plus, cela n'est-il peut-être pas lent pour les mises en page compliquées?
Daniel Rodriguez
15
findViewById()est une opération lente. L'approche fonctionne, mais au détriment des performances.
Kiril Aleksandrov
10

(C'était un commentaire à la réponse de dilettante mais ça devenait trop long ... hehe)

Bien sûr, une statique n'est pas nécessaire ici. Vous pouvez utiliser SharedPreferences pour enregistrer, au lieu de statique. Quoi qu'il en soit, la raison est de sauvegarder la progression actuelle afin qu'elle ne soit pas trop lente pour les mises en page compliquées. Car, en fait, après son utilisation une fois, ce sera plutôt rapide plus tard. Cependant, je ne pense pas que ce soit une bonne façon de le faire, car si vous devez reconstruire votre écran (par exemple, il onCreateest rappelé), vous voudrez probablement recommencer depuis le début de toute façon, éliminant le besoin de statique. Par conséquent, faites-en simplement une variable d'instance au lieu de statique.

Voici une version plus petite qui s'exécute un peu plus rapidement et pourrait être plus facile à lire:

int fID = 0;

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

Cette fonction ci-dessus devrait être suffisante. Parce que, pour autant que je sache, les identifiants générés par Android sont des milliards, donc cela reviendra probablement 1la première fois et sera toujours assez rapide. Parce que, en fait, il ne dépassera pas les ID utilisés pour en trouver un inutilisé. Cependant, la boucle est là si elle trouve réellement un ID utilisé.

Cependant, si vous souhaitez toujours enregistrer la progression entre les recréations suivantes de votre application et éviter d'utiliser de l'électricité statique. Voici la version 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;
}

Cette réponse à une question similaire devrait vous dire tout ce que vous devez savoir sur les identifiants avec Android: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: Je viens de réaliser que j'ai complètement raté la sauvegarde. Je devais être ivre.

Pimp Trizkit
la source
1
Cela devrait être la meilleure réponse. Grande utilisation du mot-clé ++ et des instructions vides;)
Aaron Gillion
9

La bibliothèque 'Compat' prend désormais également en charge la generateViewId()méthode pour les niveaux d'API antérieurs à 17.

Assurez-vous simplement d'utiliser une version de la Compatbibliothèque qui est27.1.0+

Par exemple, dans votre build.gradlefichier, mettez:

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

Ensuite, vous pouvez simplement utiliser le generateViewId()de la ViewCompatclasse au lieu de la Viewclasse comme suit:

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

Bon codage!

Alex Roussiere
la source
6

Juste un ajout à la réponse de @phantomlimb,

bien que View.generateViewId()nécessite un niveau API> = 17,
cet outil est compatible avec toutes les API.

selon le niveau d'API actuel,
il décide de la météo en utilisant ou non l'API du système.

afin que vous puissiez utiliser ViewIdGenerator.generateViewId()et View.generateViewId()en même temps et ne vous inquiétez pas d'obtenir le même identifiant

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
la source
@kenyee, l'extrait de code for (;;) { … }provient du code source Android.
fantouch
Ma compréhension est que tous les ID générés occupent l'espace numérique 0x01000000–0xffffffff, vous êtes donc assuré de ne pas vous heurter, mais je ne me souviens pas où j'ai lu cela.
Andrew Wyld
Comment réinitialiser ..generateViewId()
reegan29
@kenyee a un point, il peut entrer en collision avec les identifiants générés dans la classe View. Voir ma réponse :)
Singed
else { return View.generateViewId(); }cela ira en boucle infinie pour un niveau api inférieur à 17 appareils?
okarakose
3

Afin de générer dynamiquement le formulaire ID de vue, utilisez l'API 17

generateViewId ()

Ce qui générera une valeur appropriée pour une utilisation dans setId(int). Cette valeur n'entrera pas en collision avec les valeurs d'ID générées au moment de la construction par aapt pour R.id.

Arun C
la source
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
la source
1

J'utilise:

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

En utilisant un nombre aléatoire, j'ai toujours une énorme chance d'obtenir l'identifiant unique lors de la première tentative.

Bjørn Stenfeldt
la source
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
la source
Veuillez ajouter une description appropriée à votre réponse d'une manière plus claire pour les futurs visiteurs afin d'évaluer la valeur de votre réponse. Les réponses uniquement codées sont désapprouvées et peuvent être supprimées lors des révisions. Merci!
Luís Cruz
-1

Mon choix:

// Method that could us an unique id

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