Android: View.setID (int id) programaticamente - como evitar conflitos de identificação?

335

Estou adicionando TextViews programaticamente em um loop for e os adiciono a um ArrayList.

Como eu uso TextView.setId(int id)? Qual ID inteiro eu proponho para que não entre em conflito com outros IDs?

znq
fonte

Respostas:

147

De acordo com a Viewdocumentação

O identificador não precisa ser exclusivo na hierarquia dessa exibição. O identificador deve ser um número positivo.

Portanto, você pode usar qualquer número inteiro positivo que desejar, mas nesse caso, pode haver algumas visualizações com IDs equivalentes. Se você deseja procurar alguma exibição na hierarquia, chamar setTagcom alguns objetos-chave pode ser útil.

Nikolay Ivanov
fonte
2
Interessante, eu não sabia que os códigos não precisam ser exclusivos? Então, findViewByIdfaz qualquer garantia quanto a qual exibição é retornada se houver mais de uma com o mesmo ID? Os documentos não mencionam nada.
Matthias
26
Eu acho que os documentos mencionam algo sobre isso. Se você tiver visualizações com o mesmo ID na mesma hierarquia, findViewByIdretornará a primeira que encontrar.
Kaneda
2
@ DanyY Eu não tenho certeza se estou entendendo corretamente o que você quer dizer. O que eu tentei dizer foi que, se o layout que você configurou setContentView()tiver, digamos, 10 visualizações com o ID definido para o mesmo número de ID na mesma hierarquia , uma chamada para findViewById([repeated_id])retornará a primeira exibição definida com esse ID repetido. Foi isso que eu quis dizer.
kaneda
51
-1 Não concordo com esta resposta, porque onSaveInstanceState e onRestoreInstanceState precisam de um ID exclusivo para poder salvar / restaurar o estado da hierarquia de exibição. Se duas visualizações tiverem o mesmo ID, o estado de uma delas será perdido. Portanto, a menos que você salve o estado Exibir, você mesmo ter IDs duplicados não é uma boa ideia.
Emanuel Moecklin
3
O ID deve ser único . A partir do nível 17 da API, existe um método estático na classe View que gera um ID aleatório para usá-lo como ID de exibição. Esse método garante que o ID gerado não colide com nenhum outro ID de exibição já gerado pela ferramenta aapt durante o tempo de compilação. developer.android.com/reference/android/view/...
Mahmoud
577

A partir do nível 17 da API, você pode chamar: View.generateViewId ()

Em seguida, use View.setId (int) .

Se seu aplicativo estiver direcionado abaixo do nível da API 17, use ViewCompat.generateViewId ()

XY
fonte
2
Coloquei no meu código-fonte porque queremos oferecer suporte a níveis mais baixos de API. Está funcionando, mas o loop infinito não é uma boa prática.
SXC 19/09/2013
5
@SimonXinCheng Loops infinitos é um padrão comum usado em algoritmos sem bloqueio. Por exemplo, dê uma olhada na AtomicIntegerimplementação de métodos.
Idolon 14/11
7
Funciona bem! Uma observação: com base nas minhas experiências, você deve chamar setId () ANTES de adicionar a visualização a um layout existente, caso contrário, o OnClickListener não funcionará corretamente.
Lucas
4
Obrigado seria muito pequeno, mas obrigado. Pergunta, o que é for(;;)que eu nunca vi isso antes. Como se chama isso?
Agressor
5
@Aggressor: É um loop 'for' vazio.
sid_09 7/01/16
143

Você pode definir os IDs que serão usados ​​posteriormente na R.idaula usando um arquivo de recurso xml e permitir que o Android SDK forneça valores exclusivos durante o tempo de compilação.

 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 usá-lo no código:

myEditTextView.setId(R.id.my_edit_text_1);
Sai Aditya
fonte
20
Isso não funciona quando tenho um número desconhecido de elementos aos quais atribuirei IDs.
Mooing Duck
1
@MooingDuck Eu sei que isso está atrasado um ano, mas quando tenho que atribuir IDs únicos em tempo de execução com um número desconhecido de elementos, eu simplesmente uso "int currentId = 1000; whateverView.setId(currentId++);- Isso incrementa o ID toda vez que currentId++é usado, garantindo um ID exclusivo, e posso armazenar o IDs em meu ArrayList para acesso posterior.
Mike em SAT,
3
@ MikeinSAT: Isso só garante que eles sejam únicos entre si. Isso não significa que "não entre em conflito com outros IDs", que é uma parte essencial da pergunta.
Mooing Duck
1
Esta é a resposta vencedora, porque outros estavam dando uma olhada na ferramenta de análise de código do Android Studio e porque eu preciso de um ID que os testes saibam sem adicionar mais uma variável. Mas adicione <resources>.
Phlip 24/02
62

Também você pode definir ids.xmlem res/values. Você pode ver um exemplo exato no código de exemplo do Android.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
yenliangl
fonte
15
Aqui também é uma resposta com essa abordagem: stackoverflow.com/questions/3216294/...
Ixx
Para referência, encontrei o arquivo em: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston
28

Desde a API 17, a Viewclasse tem um método estático generateViewId() que irá

gerar um valor adequado para uso em setId (int)

Diederik
fonte
25

Isso funciona para mim:

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
fonte
Isso é um pouco mais complicado, mas eu aposto que funcionará. O uso de variáveis ​​globais em ambiente multithread certamente falhará algum dia, especialmente com vários núcleos.
maaartinus
3
Além disso, isso não é possivelmente lento para layouts complicados?
Daniel Rodriguez
15
findViewById()é uma operação lenta. A abordagem funciona, mas à custa do desempenho.
Kiril Aleksandrov
10

(Este foi um comentário à resposta do diletante, mas ficou muito tempo ... hehe)

É claro que uma estática não é necessária aqui. Você pode usar SharedPreferences para salvar, em vez de estático. De qualquer forma, o motivo é salvar o progresso atual para que não seja muito lento para layouts complicados. Porque, de fato, depois de usado uma vez, será bastante rápido mais tarde. No entanto, não acho que seja uma boa maneira de fazê-lo, porque se você precisar reconstruir sua tela novamente (digamos que onCreateseja chamado novamente), provavelmente desejará recomeçar do início, eliminando a necessidade de estática. Portanto, apenas faça dela uma variável de instância em vez de estática.

Aqui está uma versão menor que roda um pouco mais rápido e pode ser mais fácil de ler:

int fID = 0;

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

Esta função acima deve ser suficiente. Porque, até onde eu sei, os IDs gerados pelo Android estão na casa dos bilhões, então isso provavelmente retornará 1pela primeira vez e sempre será bastante rápido. Porque, na verdade, ele não passará dos IDs usados ​​para encontrar um ID não utilizado. No entanto, o loop é lá que deve realmente encontrar um ID usado.

No entanto, se você ainda deseja salvar o progresso entre as recriações subsequentes do seu aplicativo e evitar o uso de estática. Aqui está a versão 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 resposta a uma pergunta semelhante deve informar tudo o que você precisa saber sobre IDs com o Android: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: Acabei de perceber que eu estava totalmente enganado. Eu devo estar bêbado.

Pimp Trizkit
fonte
1
Essa deve ser a resposta principal. Grande uso de ++ palavra-chave e declarações vazias;)
Aaron Gillion
9

A biblioteca 'Compat' agora também suporta o generateViewId()método para níveis de API anteriores a 17.

Apenas certifique-se de usar uma versão da Compatbiblioteca que é27.1.0+

Por exemplo, no seu build.gradlearquivo, coloque:

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

Em seguida, você pode simplesmente usar o generateViewId()da ViewCompatclasse em vez da Viewclasse da seguinte maneira:

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

Feliz codificação!

Alex Roussiere
fonte
6

Apenas uma adição à resposta de @phantomlimb,

embora View.generateViewId()exija nível de API> = 17,
essa ferramenta é compatível com toda a API.

de acordo com o nível atual da API,
ele decide o clima usando a API do sistema ou não.

para que você possa usar ViewIdGenerator.generateViewId()e View.generateViewId()ao mesmo tempo e não se preocupe em obter o mesmo 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 [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
fonte
@kenyee, o snippet de código for (;;) { … }vem do Código-fonte do Android.
fantouch
Pelo que entendi, todos os IDs gerados ocupam o espaço numérico 0x01000000–0xffffffff, portanto, você garante um não confronto, mas não me lembro onde li isso.
Andrew Wyld
Como redefinir ..generateViewId()
reegan29
O @kenyee tem razão, pode colidir com os IDs gerados na classe View. Veja a minha resposta :)
Cantado em
else { return View.generateViewId(); }isso vai dar um loop infinito para o nível da API menor que 17 dispositivos?
okarakose
3

Para gerar dinamicamente a API 17 do formulário do ID da visualização, use

generateViewId ()

O que gerará um valor adequado para uso em setId(int). Este valor não colidirá com os valores de ID gerados no tempo de compilação pelo aapt para R.id.

Arun C
fonte
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
fonte
1

Eu uso:

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

Usando um número aleatório, sempre tenho uma grande chance de obter o ID exclusivo na primeira tentativa.

Bjørn Stenfeldt
fonte
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
fonte
Adicione uma descrição adequada à sua resposta de maneira que fique claro para os futuros visitantes para avaliar o valor da sua resposta. As respostas somente de código são desaprovadas e podem ser excluídas durante as revisões. Obrigado!
Luís Cruz
-1

Minha escolha:

// Method that could us an unique id

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