Android: View.setID (int id) theo chương trình - làm thế nào để tránh xung đột ID?


333

Tôi đang thêm TextViews theo chương trình trong một vòng lặp for và thêm chúng vào ArrayList.

Làm thế nào để tôi sử dụng TextView.setId(int id)? ID Integer nào tôi đưa ra để nó không xung đột với các ID khác?

Câu trả lời:


146

Theo Viewtài liệu

Mã định danh không phải là duy nhất trong hệ thống phân cấp của chế độ xem này. Mã định danh phải là một số dương.

Vì vậy, bạn có thể sử dụng bất kỳ số nguyên dương nào bạn thích, nhưng trong trường hợp này có thể có một số lượt xem với id tương đương. Nếu bạn muốn tìm kiếm một số chế độ xem theo thứ bậc, việc gọi đến setTagvới một số đối tượng chính có thể hữu ích.


2
Thật thú vị, tôi đã không biết rằng ID không cần phải là duy nhất? Vì vậy, có findViewByIdđảm bảo nào sau đó xem chế độ xem nào được trả về nếu có nhiều hơn một ID có cùng ID không? Các tài liệu không đề cập bất cứ điều gì.
Matthias

26
Tôi nghĩ rằng các tài liệu đề cập đến một cái gì đó về điều này. Nếu bạn có chế độ xem với cùng một ID trong cùng một cấu trúc phân cấp thì findViewByIdsẽ trả về lần đầu tiên nó tìm thấy.
kaneda

2
@DanyY Tôi không chắc lắm nếu tôi hiểu chính xác ý bạn là gì. Điều tôi đã cố gắng nói là nếu bố cục bạn thiết lập setContentView()có giả sử, 10 lượt xem với id của chúng được đặt thành cùng một số id trong cùng một cấu trúc phân cấp , thì một cuộc gọi findViewById([repeated_id])sẽ trả về chế độ xem đầu tiên được đặt với một id lặp lại đó. Ý tôi là thế
kaneda

51
-1 Tôi không đồng ý với câu trả lời này vì onSaveInstanceState và onRestoreInstanceState cần một id duy nhất để có thể lưu / khôi phục trạng thái phân cấp chế độ xem. Nếu hai chế độ xem có cùng id, trạng thái của một trong số chúng sẽ bị mất. Vì vậy, trừ khi bạn lưu trạng thái Xem tất cả bản thân bạn có id trùng lặp không phải là ý hay.
Emanuel Moecklin

3
Các Id phải là duy nhất . Bắt đầu từ API cấp 17, có một phương thức tĩnh trong lớp View tạo Id ngẫu nhiên để sử dụng nó làm id xem. Phương pháp đó đảm bảo rằng id được tạo sẽ không va chạm với bất kỳ id xem nào khác đã được tạo bởi công cụ aapt trong thời gian xây dựng. developer.android.com/reference/android/view/NH
Mahmoud

576

Từ API cấp 17 trở lên, bạn có thể gọi: View.generateViewId ()

Sau đó sử dụng View.setId (int) .

Nếu ứng dụng của bạn được nhắm mục tiêu thấp hơn API cấp 17, hãy sử dụng ViewCompat.generateViewId ()


2
Tôi đặt nó trong mã nguồn của mình vì chúng tôi muốn hỗ trợ các cấp API thấp hơn. Nó đang hoạt động nhưng vòng lặp vô hạn không phải là một thực hành tốt.
SXC

5
@SimonXinCheng Vòng lặp vô hạn là một mẫu phổ biến được sử dụng trong các thuật toán không chặn. Ví dụ có một cái nhìn về AtomicIntegerphương pháp thực hiện.
Thần tượng

7
Hoạt động tuyệt vời! Một lưu ý: dựa trên các thử nghiệm của tôi, bạn phải gọi setId () TRƯỚC KHI bạn thêm chế độ xem vào bố cục hiện có, nếu không OnClickListener sẽ không hoạt động chính xác.
Luke

4
Cảm ơn bạn sẽ quá nhỏ nhưng CẢM ƠN BẠN. Câu hỏi, những gì for(;;)tôi chưa bao giờ nhìn thấy trước đây. Cái đó gọi là gì?
Kẻ xâm lược

5
@Aggressor: Đó là một vòng lặp 'for' trống.
sid_09

142

Bạn có thể đặt ID bạn sẽ sử dụng sau này trong R.idlớp bằng tệp tài nguyên xml và để SDK Android cung cấp cho họ các giá trị duy nhất trong thời gian biên dịch.

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

Để sử dụng nó trong mã:

myEditTextView.setId(R.id.my_edit_text_1);

20
Điều này không hoạt động khi tôi có một số phần tử không xác định mà tôi sẽ gán id cho.
Vịt Mooing

1
@MooingDuck Tôi biết rằng đây là một năm muộn, nhưng khi tôi phải gán Id duy nhất trong thời gian chạy với một số phần tử không xác định, tôi chỉ cần sử dụng "int currentId = 1000; whateverView.setId(currentId++);- Điều đó làm tăng ID mỗi lần currentId++được sử dụng, đảm bảo ID duy nhất và tôi có thể lưu trữ ID trong ArrayList của tôi để truy cập sau.
Mike trong SAT

3
@MikeinSAT: Điều đó chỉ đảm bảo rằng họ là duy nhất giữa họ. Điều đó không làm cho nó "vì vậy nó không xung đột với các ID khác", đây là một phần quan trọng của câu hỏi.
Vịt Mooing

1
Đây là câu trả lời chiến thắng vì những người khác đã cung cấp cho công cụ phân tích mã của Android Studio một s --- phù hợp và vì tôi cần một ID kiểm tra biết mà không cần thêm một biến nào khác. Nhưng thêm vào <resources>.
Phlip

61

Ngoài ra, bạn có thể xác định ids.xmltrong res/values. Bạn có thể thấy một ví dụ chính xác trong mã mẫu của Android.

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

15
Đây cũng là một câu trả lời với cách tiếp cận này: stackoverflow.com/questions/3216294/ cấp
Ixx

Để tham khảo, tôi đã tìm thấy tệp trong: /samples/android-15/ApiDemos/src/com/example/android/apis/view/Radiogroup1.java
Taylor Edmiston


25

Điều này làm việc cho tôi:

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

Điều này phức tạp hơn một chút nhưng tôi cá là nó sẽ hoạt động. Sử dụng các biến toàn cục trong môi trường đa luồng chắc chắn sẽ thất bại một ngày nào đó, đặc biệt là với nhiều lõi.
maaartinus

3
Ngoài ra, điều này có thể chậm đối với bố cục phức tạp?
Daniel Rodriguez

15
findViewById()là một hoạt động chậm. Cách tiếp cận hoạt động, nhưng với chi phí hiệu suất.
Kiril Aleksandrov

10

(Đây là một nhận xét cho câu trả lời của Dilettante nhưng nó đã quá lâu ... hehe)

Tất nhiên một động tĩnh là không cần thiết ở đây. Bạn có thể sử dụng SharedPreferences để lưu, thay vì tĩnh. Dù bằng cách nào, lý do là để lưu tiến trình hiện tại để nó không quá chậm đối với các bố cục phức tạp. Bởi vì, trên thực tế, sau khi được sử dụng một lần, nó sẽ khá nhanh về sau. Tuy nhiên, tôi không cảm thấy đây là một cách tốt để làm điều đó bởi vì nếu bạn phải xây dựng lại màn hình của mình (nói onCreatesẽ được gọi lại), thì có lẽ bạn muốn bắt đầu lại từ đầu bằng mọi cách, loại bỏ nhu cầu tĩnh. Do đó, chỉ cần làm cho nó một biến thể hiện thay vì tĩnh.

Đây là một phiên bản nhỏ hơn chạy nhanh hơn một chút và có thể dễ đọc hơn:

int fID = 0;

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

Chức năng trên phải là đủ. Bởi vì, theo như tôi có thể nói, ID do Android tạo ra có hàng tỷ, vì vậy điều này có thể sẽ trở lại 1lần đầu tiên và luôn luôn khá nhanh. Bởi vì, nó thực sự sẽ không lặp qua các ID đã sử dụng để tìm một ID không được sử dụng. Tuy nhiên, các vòng lặp có nên nó thực sự tìm thấy một ID được sử dụng.

Tuy nhiên, nếu bạn vẫn muốn tiến trình được lưu giữa các lần tạo lại ứng dụng tiếp theo và muốn tránh sử dụng tĩnh. Đây là phiên bản 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;
}

Câu trả lời này cho một câu hỏi tương tự sẽ cho bạn biết mọi thứ bạn cần biết về ID với android: https://stackoverflow.com/a/13241629/693927

EDIT / FIX: Chỉ cần nhận ra rằng tôi hoàn toàn ủng hộ việc tiết kiệm. Tôi phải say.


1
Đây phải là câu trả lời hàng đầu. Sử dụng tuyệt vời từ khóa ++ và báo cáo trống;)
Aaron Gillion

9

Thư viện 'Compat' hiện cũng hỗ trợ generateViewId()phương thức cho các cấp API trước 17.

Chỉ cần đảm bảo sử dụng một phiên bản của Compatthư viện27.1.0+

Ví dụ: trong build.gradletệp của bạn , đặt:

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

Sau đó, bạn có thể chỉ cần sử dụng generateViewId()từ ViewCompatlớp thay vì Viewlớp như sau:

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

Chúc mừng mã hóa!


6

Chỉ là một bổ sung cho câu trả lời của @ph Phantomlimb,

trong khi View.generateViewId()yêu cầu Cấp API> = 17,
công cụ này tương thích với tất cả API.

theo Cấp độ API hiện tại,
nó quyết định thời tiết có sử dụng API hệ thống hay không.

vì vậy bạn có thể sử dụng ViewIdGenerator.generateViewId()View.generateViewId()trong cùng một thời điểm và không lo lắng về việc cùng 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 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 đoạn mã for (;;) { … }đến từ Mã nguồn Android.
fantouch

Sự hiểu biết của tôi là tất cả các ID được tạo chiếm không gian số 0x01000000 bù0xffffffff, vì vậy bạn được đảm bảo không đụng độ, nhưng tôi không thể nhớ mình đã đọc nó ở đâu.
Andrew Wyld

Cách thiết lập lại ..generateViewId()
reegan29

@kenyee có một điểm, nó có thể va chạm với id được tạo trong lớp View. Xem câu trả lời của tôi :)
hát

else { return View.generateViewId(); }điều này sẽ đi đến vòng lặp vô hạn cho cấp độ api nhỏ hơn 17 thiết bị?
okarakose

3

Để tự động tạo API dạng xem Id 17, hãy sử dụng

tạoViewId ()

Mà sẽ tạo ra một giá trị phù hợp để sử dụng trong setId(int). Giá trị này sẽ không va chạm với các giá trị ID được tạo tại thời điểm xây dựng bởi aapt for 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

Tôi sử dụng:

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

Bằng cách sử dụng một số ngẫu nhiên, tôi luôn có cơ hội lớn để có được id duy nhất trong lần thử đầu tiên.


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

Vui lòng thêm một mô tả thích hợp vào câu trả lời của bạn theo cách rõ ràng hơn cho khách truy cập trong tương lai để đánh giá giá trị câu trả lời của bạn. Các câu trả lời chỉ có mã được tán thành và có thể bị xóa trong quá trình đánh giá. Cảm ơn!
Luís Cruz

-1

Lựa chọn của tôi:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.