web-dev-qa-db-fra.com

Vues programmatiques comment définir des identifiants uniques?

Je crée dans mon application un tas de View programmatiques. Comme il semble, ils ont tous par défaut le même id=-1. Afin de travailler avec eux, j'ai besoin de générer des identifiants uniques.

J'ai essayé plusieurs approches - génération de nombres aléatoires et basée sur l'heure actuelle, mais de toute façon, il n'y a pas de garantie à 100% que différentes vues auront des identifiants différents

Vous vous demandez simplement s'il existe un moyen plus fiable de générer des produits uniques? Il y a probablement une méthode/classe spéciale?

30
Barmaley Red Star

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

tandis que View.generateViewId() nécessite un niveau d'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 l'API du système ou non.

donc vous pouvez utiliser ViewIdGenerator.generateViewId() et View.generateViewId() en même temps et ne vous inquiétez pas d'avoir 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();
        }

    }
}
41
fantouch

Je veux juste ajouter à la réponse de Kaj, à partir du niveau API 17, vous pouvez appeler

View.generateViewId ()

puis utilisez la méthode View.setId (int).

Si vous en avez besoin pour des cibles inférieures au niveau 17, voici son implémentation interne dans View.Java que vous pouvez utiliser directement dans votre projet:

private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

/**
 * Generate a value suitable for use in {@link #setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    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;
        }
    }
}

Un numéro d'identification supérieur à 0x00FFFFFF est réservé aux vues statiques définies dans les fichiers xml/res. (Très probablement 0x7f ****** du R.Java dans mes projets.)

À partir du code, en quelque sorte Android ne veut pas que vous utilisiez 0 comme id d'une vue, et il doit être retourné avant 0x01000000 pour éviter les conflits avec les ID de ressources statiques.

54
xy uber.com

Créez une classe singleton, qui a un entier atomique. Bump l'entier et renvoyer la valeur lorsque vous avez besoin d'un identifiant de vue.

L'identifiant sera unique pendant l'exécution de votre processus, mais sera réinitialisé lorsque votre processus sera redémarré.

public class ViewId {

    private static ViewId INSTANCE = new ViewId();

    private AtomicInteger seq;

    private ViewId() {
        seq = new AtomicInteger(0);
    }

    public int getUniqueId() {
        return seq.incrementAndGet();
    }

    public static ViewId getInstance() {
        return INSTANCE;
    }
}

Notez que l'identifiant peut ne pas être unique, s'il existe déjà des vues qui ont des identifiants dans la vue "graphique". Vous pouvez essayer de commencer avec un nombre qui est Integer.MAX_VALUE et le diminuer au lieu de passer de 1 à MAX_VALUE

13
Kaj

Depuis la bibliothèque de support 27.1.0, il y a generateViewId () dans ViewCompat

ViewCompat.generateViewId ()

12
margie

En ce qui concerne la solution de secours pour l'API <17, je vois que les solutions suggérées commencent à générer des ID à partir de 0 ou 1. La classe View a une autre instance de générateur, et commence également à compter à partir du numéro un, ce qui entraînera la génération de votre générateur et de celui de View les mêmes ID, et vous finirez par avoir des vues différentes avec les mêmes ID dans votre hiérarchie de vues. Malheureusement, il n'y a pas de bonne solution pour cela, mais c'est un hack qui devrait être bien documenté:

public class AndroidUtils {

/**
 *  Unique view id generator, like the one used in {@link View} class for view id generation.
 *  Since we can't access the generator within the {@link View} class before API 17, we create
 *  the same generator here. This creates a problem of two generator instances not knowing about
 *  each other, and we need to take care that one does not generate the id already generated by other one.
 *
 *  We know that all integers higher than 16 777 215 are reserved for aapt-generated identifiers
 *  (source: {@link View#generateViewId()}, so we make sure to never generate a value that big.
 *  We also know that generator within the {@link View} class starts at 1.
 *  We set our generator to start counting at 15 000 000. This gives us enough space
 *  (15 000 000 - 16 777 215), while making sure that generated IDs are unique, unless View generates
 *  more than 15M IDs, which should never happen.
 */
private static final AtomicInteger viewIdGenerator = new AtomicInteger(15000000);

/**
 * Generate a value suitable for use in {@link View#setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return generateUniqueViewId();
    } else {
        return View.generateViewId();
    }
}

private static int generateUniqueViewId() {
    while (true) {
        final int result = viewIdGenerator.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 (viewIdGenerator.compareAndSet(result, newValue)) {
            return result;
        }
    }
}

}
2
Singed