Android: View.setID (int id) secara terprogram - bagaimana cara menghindari konflik ID?

335

Saya menambahkan TextViews secara terprogram dalam for-loop dan menambahkannya ke ArrayList.

Bagaimana saya menggunakan TextView.setId(int id)? ID Integer apa yang saya buat sehingga tidak bertentangan dengan ID lain?

znq
sumber

Jawaban:

147

Menurut Viewdokumentasi

Pengidentifikasi tidak harus unik dalam hierarki tampilan ini. Pengidentifikasi harus berupa angka positif.

Jadi, Anda dapat menggunakan bilangan bulat positif apa pun yang Anda suka, tetapi dalam hal ini ada beberapa tampilan dengan id yang setara. Jika Anda ingin mencari beberapa tampilan dalam hierarki panggilan setTagdengan beberapa objek utama mungkin berguna.

Nikolay Ivanov
sumber
2
Menarik, saya tidak tahu bahwa ID tidak perlu unik? Jadi, apakah findViewByIdada jaminan untuk tampilan yang dikembalikan jika ada lebih dari satu dengan ID yang sama? Dokumen tidak menyebutkan apa pun.
Matthias
26
Saya pikir dokter menyebutkan sesuatu tentang ini. Jika Anda memiliki tampilan dengan ID yang sama dalam hierarki yang sama maka findViewByIdakan mengembalikan yang pertama kali ditemukannya.
kaneda
2
@DanyY Saya tidak yakin apakah saya mengerti dengan benar apa yang Anda maksud. Apa yang saya coba katakan adalah bahwa jika tata letak yang Anda set setContentView()memiliki katakanlah, 10 tampilan dengan id mereka diatur ke nomor id yang sama dalam hierarki yang sama , maka panggilan untuk findViewById([repeated_id])akan mengembalikan tampilan pertama diatur dengan satu id yang diulang. Itu yang saya maksud.
kaneda
51
-1 Saya tidak setuju dengan jawaban ini karena onSaveInstanceState dan onRestoreInstanceState memerlukan id unik untuk dapat menyimpan / mengembalikan keadaan hierarki tampilan. Jika dua tampilan memiliki id yang sama, status salah satunya akan hilang. Jadi, kecuali Anda menyimpan kondisi Tampilan semua diri Anda memiliki duplikat id bukan ide yang baik.
Emanuel Moecklin
3
The Id harus unik . Mulai dari API level 17 ada metode statis di kelas Tampilan yang menghasilkan Id acak untuk menggunakannya sebagai view id. Metode itu memastikan bahwa id yang dihasilkan tidak akan bertabrakan dengan id tampilan lain yang telah dihasilkan oleh alat aapt selama waktu pembuatan. developer.android.com/reference/android/view/…
Mahmoud
577

Dari API level 17 ke atas, Anda dapat menghubungi: View.generateViewId ()

Kemudian gunakan View.setId (int) .

Jika aplikasi Anda ditargetkan lebih rendah dari API level 17, gunakan ViewCompat.generateViewId ()

XY
sumber
2
Saya memasukkannya ke dalam kode sumber saya karena kami ingin mendukung tingkat API yang lebih rendah. Ini bekerja tetapi infinite loop bukanlah praktik yang baik.
SXC
5
@SimonXinCheng Infinite loop adalah pola umum yang digunakan dalam algoritma non-blocking. Sebagai contoh, lihat AtomicIntegerimplementasi metode.
Idolon
7
Bagus sekali! Satu catatan: berdasarkan eksperimen saya, Anda harus memanggil setId () SEBELUM Anda menambahkan tampilan ke tata letak yang ada, atau OnClickListener tidak akan berfungsi dengan baik.
Luke
4
Terima kasih terlalu kecil tapi TERIMA KASIH. Pertanyaan, apa yang for(;;)belum pernah saya lihat sebelumnya. Apa itu namanya?
Aggressor
5
@Aggressor: Ini loop kosong 'untuk'.
sid_09
143

Anda dapat mengatur ID yang akan Anda gunakan nanti di R.idkelas menggunakan file sumber daya xml, dan biarkan Android SDK memberi mereka nilai unik selama waktu kompilasi.

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

Untuk menggunakannya dalam kode:

myEditTextView.setId(R.id.my_edit_text_1);
Sai Aditya
sumber
20
Ini tidak berfungsi ketika saya memiliki jumlah elemen yang tidak diketahui yang akan saya berikan id.
Mooing Duck
1
@ MoingDuck Saya tahu ini terlambat satu tahun, tetapi ketika saya harus menetapkan Id unik saat runtime dengan jumlah elemen yang tidak diketahui, saya cukup menggunakan "int currentId = 1000; whateverView.setId(currentId++);- Itu menambah ID setiap kali currentId++digunakan, memastikan ID unik, dan saya dapat menyimpan ID di ArrayList saya untuk akses nanti.
Mike dalam SAT
3
@MikeinSAT: Itu hanya menjamin bahwa mereka unik di antara mereka sendiri. Itu tidak membuatnya "jadi tidak bertentangan dengan ID lain", yang merupakan bagian penting dari pertanyaan.
Mooing Duck
1
Ini adalah jawaban yang menang karena orang lain memberikan alat analisis kode Android Studio s --- fit, dan karena saya memerlukan ID yang tes tahu tanpa menambahkan variabel lain. Tapi tambahkan <resources>.
Phlip
62

Juga Anda dapat menentukan ids.xmldi res/values. Anda dapat melihat contoh yang tepat dalam kode sampel android.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
yenliangl
sumber
15
Berikut ini juga jawaban dengan pendekatan ini: stackoverflow.com/questions/3216294/…
Ixx
Untuk referensi, saya menemukan file di: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java
Taylor Edmiston
28

Sejak API 17, Viewkelas memiliki metode statis generateViewId() yang akan

menghasilkan nilai yang cocok untuk digunakan di setId (int)

Diederik
sumber
25

Ini bekerja untuk saya:

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++;  
}
penggemar
sumber
Ini sedikit lebih rumit tetapi saya berani bertaruh itu akan berhasil. Menggunakan variabel global dalam lingkungan multithreaded pasti akan gagal suatu hari, terutama dengan banyak core.
maaartinus
3
Juga, bukankah ini mungkin lambat untuk tata letak yang rumit?
Daniel Rodriguez
15
findViewById()adalah operasi yang lambat. Pendekatannya berhasil, tetapi dengan biaya kinerja.
Kiril Aleksandrov
10

(Ini adalah komentar untuk jawaban dilettante tapi terlalu panjang ... hehe)

Tentu saja statis tidak diperlukan di sini. Anda bisa menggunakan SharedPreferences untuk menyimpan, bukan statis. Either way, alasannya adalah untuk menyimpan kemajuan saat ini sehingga tidak terlalu lambat untuk tata letak yang rumit. Karena, pada kenyataannya, setelah digunakan sekali, itu akan agak cepat nantinya. Namun, saya tidak merasa ini adalah cara yang baik untuk melakukannya karena jika Anda harus membangun kembali layar Anda lagi (katakanlahonCreate dipanggil lagi), maka Anda mungkin ingin memulai dari awal lagi, menghilangkan kebutuhan untuk statis. Karena itu, buat saja variabel instan bukan statis.

Ini adalah versi yang lebih kecil yang berjalan sedikit lebih cepat dan mungkin lebih mudah dibaca:

int fID = 0;

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

Fungsi di atas ini harus memadai. Karena, sejauh yang saya tahu, ID yang dihasilkan android ada dalam miliaran, jadi ini mungkin akan kembali 1pertama kali dan selalu sangat cepat. Karena, itu sebenarnya tidak akan melewati ID yang digunakan untuk menemukan yang tidak digunakan. Namun, pengulangan itu harus ada jika benar-benar menemukan ID yang digunakan.

Namun, jika Anda tetap ingin progres disimpan di antara rekreasi ulang aplikasi Anda berikutnya, dan ingin menghindari penggunaan statis. Ini adalah versi 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;
}

Jawaban untuk pertanyaan serupa ini akan memberi tahu Anda segala yang perlu Anda ketahui tentang ID dengan android: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: Baru sadar saya benar-benar melakukan kesalahan save. Saya pasti mabuk.

Pimp Trizkit
sumber
1
Ini harus menjadi jawaban teratas. Penggunaan kata kunci ++ dan pernyataan kosong;)
Aaron Gillion
9

Pustaka 'Compat' sekarang juga mendukung generateViewId()metode untuk level API sebelum 17.

Pastikan untuk menggunakan versi Compatpustaka yang ada27.1.0+

Misalnya, di build.gradlefile Anda , masukkan:

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

Maka Anda bisa menggunakan generateViewId()dari ViewCompatkelas bukan Viewkelas sebagai berikut:

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

Selamat coding!

Alex Roussiere
sumber
6

Hanya tambahan untuk jawaban @ phantomlimb,

saat View.generateViewId()membutuhkan API Level> = 17,
alat ini kompatibel dengan semua API.

menurut Level API saat ini,
ia memutuskan cuaca menggunakan sistem API atau tidak.

jadi Anda bisa menggunakan ViewIdGenerator.generateViewId()dan View.generateViewId()dalam waktu yang bersamaan dan tidak perlu khawatir mendapatkan id yang sama

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
sumber
@kenyee cuplikan kode for (;;) { … }berasal dari Kode Sumber Android.
fantouch
Pemahaman saya adalah semua ID yang dihasilkan menempati ruang angka 0x01000000–0xffffffff, jadi Anda dijamin tidak bentrok, tapi saya tidak ingat di mana saya membaca ini.
Andrew Wyld
Cara mengatur ulang ..generateViewId()
reegan29
@kenyee ada benarnya, itu bisa bertabrakan dengan id yang dihasilkan dalam kelas View. Lihat jawaban saya :)
Singed
else { return View.generateViewId(); }ini akan menuju loop tak terbatas untuk level api yang lebih kecil dari 17 perangkat?
okarakose
3

Untuk menghasilkan secara dinamis bentuk Id Penggunaan API 17 digunakan

generateViewId ()

Yang akan menghasilkan nilai yang cocok untuk digunakan di setId(int). Nilai ini tidak akan bertabrakan dengan nilai ID yang dihasilkan saat membangun oleh aapt untuk R.id.

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

Saya menggunakan:

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

Dengan menggunakan nomor acak saya selalu memiliki peluang besar untuk mendapatkan id unik dalam upaya pertama.

Bjørn Stenfeldt
sumber
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
sumber
Silakan tambahkan deskripsi yang tepat untuk jawaban Anda dengan cara yang lebih jelas bagi pengunjung masa depan untuk menilai nilai jawaban Anda. Jawaban khusus kode hanya disukai dan dapat dihapus selama ulasan. Terima kasih!
Luís Cruz
-1

Pilihan saya:

// Method that could us an unique id

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