안드로이드 : 프로그래밍 방식으로 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.