Android: Programlı olarak View.setID (int id) - kimlik çakışmaları nasıl önlenir?


335

Bir for-loop programlı olarak TextViews ekliyorum ve bir ArrayList onları ekleyin.

Nasıl Kullanırım TextView.setId(int id)? Diğer kimliklerle çakışmaması için hangi Tamsayı Kimliği ile karşılaşıyorum?

Yanıtlar:


147

ViewBelgelere göre

Tanımlayıcının bu görünümün hiyerarşisinde benzersiz olması gerekmez. Tanımlayıcı pozitif bir sayı olmalıdır.

Bu nedenle, istediğiniz herhangi bir pozitif tamsayıyı kullanabilirsiniz, ancak bu durumda eşdeğer kimliklere sahip bazı görünümler olabilir. Hiyerarşide bazı görünümleri aramak istiyorsanız setTag, bazı önemli nesnelerle çağrı yapmak kullanışlı olabilir.


2
İlginç, kimliklerin benzersiz olması gerekmediğinin farkında değildim? Yani yapar findViewByIddaha aynı kimlikle olandan varsa görünüm döndürülür sonra da hangi herhangi bir garanti? Dokümanlar hiçbir şeyden bahsetmiyor.
Matthias

26
Bence dokümanlar bu konuda bir şeyden bahsediyorlar. Aynı hiyerarşide aynı kimliğe sahip görünümleriniz varsa findViewByIdbulduğu ilk değeri döndürür.
kaneda

2
@DanyY Ne demek istediğinizi doğru anladığımdan emin değilim. Söylemeye çalıştığım şey, belirlediğiniz düzen setContentView()diyelim, kimlikleri aynı hiyerarşide aynı kimlik numarasına ayarlanmış 10 görünüm , o zaman bir çağrı findViewById([repeated_id])bu tekrarlanan kimliğe sahip ilk görünüm kümesini döndürecektir. Demek istediğim şey o.
kaneda

51
-1 Bu yanıta katılmıyorum çünkü onSaveInstanceState ve onRestoreInstanceState, görünüm hiyerarşisinin durumunu kaydetmek / geri yüklemek için benzersiz bir kimliğe ihtiyaç duyuyor. İki görünüm aynı kimliğe sahipse, bunlardan birinin durumu kaybolur. Bu nedenle, Görünüm durumunu kaydetmezseniz, yinelenen kimliklere sahip olmanız iyi bir fikir değildir.
Emanuel Moecklin

3
Kimliği benzersiz olmalıdır . API düzey 17'den başlayarak , View sınıfında view id olarak kullanmak için rasgele bir kimlik üreten statik bir yöntem vardır. Bu yöntem, oluşturulan kimliğin oluşturma süresi boyunca aapt aracı tarafından önceden oluşturulmuş başka bir görünüm kimliğiyle çarpışmamasını sağlar. developer.android.com/reference/android/view/…
Mahmoud

577

API düzey 17 ve üzeri sürümlerden şunu arayabilirsiniz: View.generateViewId ()

Sonra View.setId (int) kullanın .

Uygulamanız API 17 düzeyinden düşükse, ViewCompat.generateViewId () kullanın


2
Kaynak koduma koydum çünkü daha düşük API düzeylerini desteklemek istiyoruz. Çalışıyor ama sonsuz döngü iyi bir uygulama değil.
SXC

5
@SimonXinCheng Sonsuz döngüler, engellemeyen algoritmalarda kullanılan yaygın bir modeldir. Örneğin AtomicIntegeryöntemlerin uygulanmasına bir göz atın .
Idolon

7
Harika çalışıyor! Bir not: deneylerime dayanarak, görünümü mevcut bir mizanpaja eklemeden ÖNCE setId () öğesini çağırmanız gerekir, aksi takdirde OnClickListener düzgün çalışmaz.
Luke

4
Çok küçük olurdu ama TEŞEKKÜRLER. Soru, for(;;)bunu daha önce hiç görmedim. Buna ne denir?
Saldırgan

5
@Aggressor: Boş bir 'for' döngüsü.
sid_09

143

Daha sonra R.idsınıfta kullanacağınız kimlikleri bir xml kaynak dosyası kullanarak ayarlayabilir ve Android SDK'nın derleme süresi boyunca onlara benzersiz değerler vermesine izin verebilirsiniz.

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

Kodda kullanmak için:

myEditTextView.setId(R.id.my_edit_text_1);

20
Kimlik atayacağım bilinmeyen sayıda öğem olduğunda bu çalışmaz.
Mooing Ördek

1
@MooingDuck Bunun bir yıl geç olduğunu biliyorum, ancak bilinmeyen sayıda öğe ile çalışma zamanında benzersiz Kimleler atamak zorunda kaldığımda, basitçe kullanıyorum "int currentId = 1000; whateverView.setId(currentId++);- Bu, her currentId++kullanıldığında kimliği artırır, benzersiz bir kimlik sağlar ve Daha sonra erişmek için ArrayList'imdeki kimlikler.
Mike, SAT

3
@MikeinSAT: Bu sadece kendi aralarında benzersiz olduklarını garanti eder. Bu, sorunun önemli bir parçası olan "diğer kimliklerle çakışmadığı" anlamına gelmez.
Mooing Duck

1
Bu kazanan cevaptır çünkü diğerleri Android Studio'nun kod analizi aracına s --- fit veriyor ve başka bir değişken eklemeden test eden bir kimliğe ihtiyacım olduğu için. Ama ekleyin <resources>.
Phlip

62

Ayrıca tanımlayabilirsiniz ids.xmliçinde res/values. Android'in örnek kodunda tam bir örnek görebilirsiniz.

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

15
İşte bu yaklaşımla da bir cevap: stackoverflow.com/questions/3216294/…
Ixx

Başvuru için dosyayı buldum: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston


25

Bu benim için çalışıyor:

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

Bu biraz daha karmaşık ama işe yarayacağına bahse girerim. Çok iş parçacıklı ortamda küresel değişkenlerin kullanılması, özellikle birden çok çekirdeğe sahip bir gün mutlaka başarısız olacaktır.
maaartinus

3
Ayrıca, karmaşık düzenler için bu muhtemelen yavaş değil mi?
Daniel Rodriguez

15
findViewById()yavaş bir işlemdir. Yaklaşım işe yarar ama performans pahasına.
Kiril Aleksandrov

10

(Bu dilettante cevabının bir yorumuydu ama çok uzun sürdü ... hehe)

Tabii ki burada bir statik gerekli değildir. Statik yerine kaydetmek için SharedPreferences kullanabilirsiniz. Her iki durumda da, mevcut ilerlemeyi kaydetmek, böylece karmaşık düzenler için çok yavaş olmamasını sağlamaktır. Çünkü, aslında, bir kez kullanıldıktan sonra, daha sonra oldukça hızlı olacaktır. Ancak, bunu yapmak için iyi bir yol olduğunu düşünmüyorum, çünkü ekranınızı tekrar yeniden oluşturmanız gerekiyorsa ( onCreatetekrar çağrılır), o zaman muhtemelen baştan başlamak istersiniz, statik ihtiyacını ortadan kaldırırsınız. Bu nedenle, bunu statik yerine bir örnek değişkeni yapın.

İşte biraz daha hızlı çalışan ve daha kolay okunabilen daha küçük bir sürüm:

int fID = 0;

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

Yukarıdaki fonksiyon yeterli olmalıdır. Çünkü, söyleyebildiğim kadarıyla, android tarafından oluşturulan kimlikler milyarlarca, bu yüzden bu muhtemelen 1ilk kez geri dönecek ve her zaman oldukça hızlı olacaktır. Çünkü, kullanılmayan bir kimlik bulmak için kullanılan kimlikleri geçmeyecek. Bununla birlikte, çevrim olduğu aslında kullanılmış bir kimliği var bulmalıdır.

Bununla birlikte, uygulamanızın sonraki rekreasyonları arasında ilerleme kaydedilmesini istiyorsanız ve statik kullanmaktan kaçınmak istiyorsanız. İşte SharedPreferences sürümü:

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

Benzer bir soruya verilen bu cevap, Android ile kimlikler hakkında bilmeniz gereken her şeyi size söylemelidir: https://stackoverflow.com/a/13241629/693927

DÜZENLEME / DÜZELTME: Sadece kurtarmayı tamamen kandırdığımı fark ettim. Sarhoş olmalıydım.


1
Bu en iyi cevap olmalı. ++ anahtar kelimesinin ve boş ifadelerin büyük kullanımı;)
Aaron Gillion

9

'Compat' kitaplığı artık generateViewId()17'den önceki API seviyeleri için yöntemi de desteklemektedir .

Sadece Compatkütüphanenin bir sürümünü kullandığınızdan emin olun.27.1.0+

Örneğin, build.gradledosyanıza şunu koyun:

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

Sonra basitçe kullanabilirsiniz generateViewId()gelen ViewCompatyerine sınıfında Viewaşağıdaki gibi sınıfta:

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

Mutlu kodlama!


6

@Phantomlimb cevabına sadece bir ek,

ise View.generateViewId()API Seviye> = 17 gerektirir,
bu aracı tüm API ile compatibe olduğunu.

mevcut API Seviyesine göre,
sistem API'sini kullanarak hava durumuna karar verir.

böylece aynı anda ve kullanabilirsiniz ViewIdGenerator.generateViewId()ve View.generateViewId()aynı kimliği alma konusunda endişelenmeyin

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 kod pasajı for (;;) { … }Android Kaynak Kodundan gelir.
fantouch

Anladığım kadarıyla, oluşturulan tüm kimlikler 0x01000000–0xffffffff sayı alanını kaplıyor, bu nedenle çakışma olmadığını garanti edersiniz, ancak bunu nerede okuduğumu hatırlayamıyorum.
Andrew Wyld

Nasıl sıfırlanır ..generateViewId()
reegan29

@kenyee bir noktaya sahiptir, View sınıfında oluşturulan kimliklerle çarpışabilir. Cevabımı :) Bkz
Singed

else { return View.generateViewId(); }17 cihazdan daha küçük api seviyesi için bu sonsuz döngüye girecek mi?
okarakose

3

View Id form API 17'yi dinamik olarak oluşturmak için şunu kullanın:

generateViewId ()

Hangi kullanım için uygun bir değer üretecektir setId(int). Bu değer, aapt tarafından derleme zamanında oluşturulan kimlik değerleriyle çarpışmayacaktır 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

Kullanırım:

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

Rastgele bir sayı kullanarak, ilk denemede benzersiz kimliği alma şansım her zaman büyüktür.


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

Lütfen cevabınızın değerini, gelecekteki ziyaretçilere cevabınızın değerini değerlendirmek için daha açık bir şekilde ekleyin. Yalnızca kod yanıtları kaşlarını çatmıştır ve incelemeler sırasında silinebilir. Teşekkürler!
Luís Cruz

-1

Benim seçimim:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.