안드로이드 : 프로그래밍 방식으로 View.setID (int id)-ID 충돌을 피하는 방법?


335

프로그래밍 방식으로 for-loop에 TextView를 추가하고 ArrayList에 추가합니다.

어떻게 사용 TextView.setId(int id)합니까? 다른 ID와 충돌하지 않도록 어떤 Integer ID를 사용해야합니까?

답변:


147

View설명서 에 따르면

이 뷰의 계층에서 식별자가 고유하지 않아도됩니다. 식별자는 양수 여야합니다.

따라서 원하는 양의 정수를 사용할 수 있지만이 경우 동등한 id를 가진 일부 뷰가있을 수 있습니다. setTag키 객체로 호출하는 계층 구조에서 일부 뷰를 검색하려는 경우 편리 할 수 ​​있습니다.


2
흥미롭게도, ID가 고유 할 필요는 없다는 것을 몰랐습니다. 따라서 findViewById동일한 ID를 가진 뷰가 두 개 이상있는 경우 어떤 뷰가 반환되는지를 보증합니까? 문서에는 언급이 없습니다.
— Matthias

26
나는 문서가 이것에 대해 언급했다고 생각한다. 동일한 계층 구조에 동일한 ID를 가진 뷰가 있으면 findViewById가장 먼저 찾은 것을 반환합니다.
— kaneda

2
@DanyY 나는 당신이 무슨 뜻인지 올바르게 이해하고 있는지 확실하지 않습니다. 내가 말한 것은 설정 한 레이아웃 setContentView()에 동일한 계층 구조 에서 동일한 ID 번호로 ID가 설정된 10 개의 뷰가 있다고 가정 하면 호출하면 findViewById([repeated_id])해당 하나의 반복 된 ID로 첫 번째 뷰 세트가 반환됩니다. 그게 내 뜻이야
— kaneda 2016 년

51
-1 onSaveInstanceState 및 onRestoreInstanceState에 뷰 계층의 상태를 저장 / 복원 할 수있는 고유 ID가 필요하기 때문에이 답변에 동의하지 않습니다. 두 개의 뷰가 동일한 ID를 갖는 경우 그 중 하나의 상태가 유실됩니다. 따라서 View 상태를 저장하지 않으면 ID가 중복되는 것은 좋지 않습니다.
— Emanuel Moecklin

3
ID가 고유해야합니다 . API 레벨 17 부터 View 클래스에는 임의의 ID를 생성하여이를 뷰 ID로 사용하는 정적 메소드가 있습니다. 이 방법은 생성 된 ID가 빌드 시간 동안 aapt 도구에 의해 이미 생성 된 다른보기 ID와 충돌하지 않도록합니다. developer.android.com/reference/android/view/…
— Mahmoud

576

API 레벨 17 이상에서 다음을 호출 할 수 있습니다. View.generateViewId ()

그런 다음 View.setId (int)를 사용하십시오. .

앱이 API 레벨 17보다 낮게 타겟팅 된 경우 ViewCompat.generateViewId ()를 사용하십시오.


2
더 낮은 API 레벨을 지원하기 위해 소스 코드에 넣었습니다. 작동하지만 무한 루프는 좋은 습관이 아닙니다.
— SXC

5
@SimonXinCheng 무한 루프는 비 차단 알고리즘에 사용되는 일반적인 패턴입니다. 예를 들어 AtomicInteger메소드 구현을 살펴보십시오 .
— Idolon

7
잘 작동합니다! 참고 사항 : 내 실험에 따라 뷰를 기존 레이아웃에 추가하기 전에 setId ()를 호출해야합니다. 그렇지 않으면 OnClickListener가 제대로 작동하지 않습니다.
— 누가 복음

4
너무 작지만 감사합니다. 질문, for(;;)내가 전에 본 적이없는 것. 그게 뭐야?
— 침략자

5
@Aggressor : 빈 'for'루프입니다.
— sid_09

143

나중에 R.idxml 리소스 파일을 사용하여 클래스 에서 사용할 ID를 설정 하고 컴파일하는 동안 Android SDK가 고유 한 값을 제공하도록 할 수 있습니다.

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

코드에서 사용하려면 :

myEditTextView.setId(R.id.my_edit_text_1);

20
ID를 할당 할 알 수없는 요소가있을 때 작동하지 않습니다.
— Mooing Duck

1
@MooingDuck 나는 이것이 1 년 늦었다는 것을 알고 있지만 런타임에 알 수없는 수의 요소로 고유 ID를 할당해야 할 때 간단히 사용합니다 "int currentId = 1000; whateverView.setId(currentId++);- 사용 할 때마다 ID가 증가하여 currentId++고유 ID를 보장하며 나중에 액세스 할 수 있도록 내 ArrayList의 ID
— Mike

3
@ MikeikeSAT : 그것은 그들 사이에서 독특하다는 것을 보장합니다. 그렇다고해서 다른 ID와 충돌하지는 않습니다. 이는 질문의 핵심 부분입니다.
— Mooing Duck

1
다른 사람들이 Android Studio의 코드 분석 도구를 적합하게 만들고 또 다른 변수를 추가하지 않고 테스트하는 ID가 필요하기 때문에 이것이 답입니다. 하지만 추가하십시오 <resources>.
— Phlip

62

또한 ids.xml에서 정의 할 수 있습니다 res/values. 안드로이드의 샘플 코드에서 정확한 예제를 볼 수 있습니다.

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

15
이 접근 방식에 대한 답변은 다음과 같습니다. stackoverflow.com/questions/3216294/…
— Ixx

참고로 파일을 /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java에서 찾았습니다
— Taylor Edmiston


25

이것은 나를 위해 작동합니다 :

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

이것은 조금 더 복잡하지만 작동 할 것이라고 확신합니다. 다중 스레드 환경에서 전역 변수를 사용하면 언젠가는 특히 여러 코어에서 실패 할 것입니다.
— maaartinus

3
또한 복잡한 레이아웃에서는 느리지 않습니까?
— Daniel Rodriguez

15
findViewById()느린 작업입니다. 이 방법은 효과가 있지만 성능이 저하됩니다.
— Kiril Aleksandrov

10

(이것은 dilettante의 답변에 대한 의견이지만 너무 길었습니다 ... hehe)

물론 정적은 필요하지 않습니다. 정적 대신 SharedPreferences를 사용하여 저장할 수 있습니다. 어느 쪽이든, 이유는 복잡한 진행을 위해 너무 느리지 않도록 현재 진행 상황을 저장하는 것입니다. 실제로 한 번 사용한 후에는 나중에 더 빠르기 때문입니다. 그러나 화면을 다시 작성 해야하는 경우 (예 onCreate: 다시 호출되는 경우) 어쨌든 처음부터 다시 시작하여 정적이 필요 하지 않기 때문에 이것이 좋은 방법이라고 생각하지 않습니다 . 따라서 정적 대신 인스턴스 변수로 만드십시오.

다음은 조금 더 빠르게 실행되고 읽기 쉬운 작은 버전입니다.

int fID = 0;

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

이 기능으로 충분합니다. 내가 알 수있는 한 안드로이드 생성 ID는 수십억 개이므로 1처음으로 돌아올 것이고 항상 빠릅니다. 실제로 사용되지 않은 ID를 찾기 위해 사용 된 ID를 지나치지 않기 때문입니다. 그러나 루프 는 실제로 사용 된 ID를 찾아야합니다.

그러나 앱의 후속 레크리에이션 사이에 진행 상황을 저장하고 정적 사용을 피하려는 경우. 다음은 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;
}

비슷한 질문에 대한이 답변은 안드로이드 ID에 대해 알아야 할 모든 것을 알려줍니다. https //.com/a/13241629/693927

편집 / 수정 : 방금 내가 완전히 저장을 깨달았다는 것을 깨달았습니다. 나는 술에 취했을 것입니다.


1
이것이 최고의 답변이어야합니다. ++ 키워드와 빈 문장의 큰 사용;)
— Aaron Gillion

9

'Compat'라이브러리는 이제 generateViewId()17 이전의 API 레벨 방법 도 지원합니다 .

Compat라이브러리 버전을 사용하십시오.27.1.0+

예를 들어 build.gradle파일에 다음을 입력하십시오.

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

그런 다음 다음 generateViewId()과 같이 ViewCompat클래스 대신 클래스 에서 간단하게 사용할 수 있습니다 View.

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

행복한 코딩!


6

@phantomlimb의 답변에 추가 된 것,

View.generateViewId()API 레벨> = 17이 필요 하지만
이 이 도구는 모든 API와 호환됩니다.

현재 API 레벨에 따라
시스템 API를 사용하여 날씨를 결정합니다.

그래서 당신은 동시에 사용할 수 ViewIdGenerator.generateViewId()있고 View.generateViewId()동일한 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 코드 스 니펫 for (;;) { … }은 Android 소스 코드에서 제공됩니다.
— fantouch

내 이해는 생성 된 모든 ID가 숫자 공간 0x01000000–0xffffffff를 차지하므로 충돌이 없음을 보장하지만 어디서 읽었는지 기억할 수 없습니다.
— Andrew Wyld

재설정 방법 ..generateViewId()
— reegan29

@kenyee는 요점을 가지고 있으며 View 클래스 내에서 생성 된 ID와 충돌 할 수 있습니다. 내 답변보기 :)
— 노래

else { return View.generateViewId(); }이것은 17 장치보다 작은 API 레벨에 대해 무한 루프가 될 것입니까?
— okarakose

3

View Id 양식 API 17을 동적으로 생성하려면

generateViewId ()

에 사용하기에 적합한 값을 생성합니다 setId(int). 이 값은 빌드시 aapt에 의해 생성 된 ID 값과 충돌하지 않습니다 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

나는 사용한다:

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

난수를 사용하면 항상 첫 번째 시도에서 고유 ID를 얻을 수있는 큰 기회가 있습니다.


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

향후 방문자가보다 명확하게 답변의 가치를 평가할 수 있도록 답변에 적절한 설명을 추가하십시오. 코드 전용 답변은 눈살을 찌푸리고 검토 중에 삭제할 수 있습니다. 감사!
— Luís Cruz

-1

내 선택:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.