Android:プログラムでView.setID(int id)-IDの競合を回避する方法?


335

プログラムでTextViewをforループに追加し、それらをArrayListに追加しています。

どうやって使うのTextView.setId(int id)?他のIDと競合しないように、どのような整数IDを考え出すのですか?

回答:


147

Viewドキュメントによると

識別子は、このビューの階層内で一意である必要はありません。識別子は正の数でなければなりません。

したがって、任意の正の整数を使用できますが、この場合、同等のIDを持つビューがいくつか存在する可能性があります。階層内のビューを検索する場合はsetTag、いくつかのキーオブジェクトを使用してを呼び出すと便利です。


2
興味深いことに、IDが一意である必要がないことを知りませんでしたか?それでfindViewById、同じIDを持つビューが複数ある場合にどのビューが返されるかについて、何か保証はありますか?ドキュメントは何も言及していません。
Matthias

26
ドキュメントはこれについて何か言及していると思います。同じ階層に同じIDのビューがある場合はfindViewById、最初に見つかったを返します。
カネダ

2
@DanyY私があなたの意味を正しく理解しているかどうか、私にはよくわかりません。私が言おうとしたのは、設定したレイアウトでsetContentView()、たとえば、IDが同じ階層の同じID番号に設定された10個のビューがある場合、を呼び出すとfindViewById([repeated_id])、その1つの繰り返しIDを持つ最初のビューセットが返されるということです。そういう意味です。
kaneda 2013年

51
-1 onSaveInstanceStateとonRestoreInstanceStateは、ビュー階層の状態を保存/復元するために一意のIDを必要とするため、この回答に同意しません。2つのビューのIDが同じである場合、そのうちの1つのビューの状態は失われます。そのため、Viewステートを保存しない限り、IDが重複していることはお勧めできません。
Emanuel Moecklin 14

3
IDは一意でなければなりませんAPIレベル17以降、Viewクラスには、それをビューIDとして使用するランダムIDを生成する静的メソッドがあります。このメソッドは、生成されたIDが、ビルド時にaaptツールによってすでに生成された他のビューIDと衝突しないことを保証します。 developer.android.com/reference/android/view/...
マフムード

577

APIレベル17以上から、次を呼び出すことができます:View.generateViewId()

次に、View.setId(int)を使用します。

アプリのターゲットがAPIレベル17より低い場合は、ViewCompat.generateViewId()を使用します


2
低いAPIレベルをサポートしたいので、ソースコードに入れました。動作していますが、無限ループはお勧めできません。
SXC 2013

5
@SimonXinCheng無限ループは、非ブロッキングアルゴリズムで使用される一般的なパターンです。たとえば、AtomicIntegerメソッドの実装を見てみましょう。
Idolon 2013年

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、2015

1
@MooingDuck私はこれが1年遅れであることを知っていますが、実行時に不明な数の要素を使用して一意のIDを割り当てる必要がある場合は、単に使用します"int currentId = 1000; whateverView.setId(currentId++);- 使用するたびにIDをインクリメントしcurrentId++、一意のIDを確保して、後でアクセスできるように、ArrayListのID。
Mike in SAT

3
@MikeinSAT:それはそれらがそれらの間でユニークであることを保証するだけです。これは、「他のIDと競合しないようにする」ことにはなりません。これは、質問の重要な部分です。
Mooing Duck 2016

1
他の人がAn​​droid Studioのコード分析ツールに適したツールを提供していて、さらに別の変数を追加せずにテストで認識できるIDが必要なため、これが勝利の答えです。しかし、追加し<resources>ます。
Phlip

62

また、あなたは定義することができますids.xmlの中でres/values。正確な例は、androidのサンプルコードで確認できます。

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 '28


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 2012

3
また、複雑なレイアウトの場合、これが遅くなる可能性はありませんか?
Daniel Rodriguez

15
findViewById()遅い操作です。このアプローチは機能しますが、パフォーマンスは犠牲になります。
キリルアレクサンドロフ

10

(これはdilettanteの回答へのコメントでしたが、長くなりすぎました... hehe)

もちろん、ここではstaticは必要ありません。静的ではなく、SharedPreferencesを使用して保存できます。いずれにせよ、理由は現在の進行状況を保存して、複雑なレイアウトで遅くなりすぎないようにするためです。実際には、一度使用した後は、後でかなり高速になるからです。ただし、画面を再構築する必要がある場合(たとえばonCreate、再度呼び出される場合)は、とにかく最初からやり直して静的の必要性を排除する必要があるため、これは良い方法ではないと思います。したがって、静的ではなくインスタンス変数にするだけです。

以下は、実行速度が少し速く、読みやすい小さなバージョンです。

int fID = 0;

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

この上記の機能で十分です。私の知る限りでは、Androidで生成された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;
}

同様の質問に対するこの回答は、AndroidでのIDについて知っておく必要があるすべてを教えてくれるはずです。https//stackoverflow.com/a/13241629/693927

編集/修正:私は完全に保存を間違えたことに気づきました。酔っていたに違いない。


1
これが一番の答えになるはずです。++キーワードと空のステートメントの
優れた

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ソースコードから取得されます。
ファンタッチ2014年

私の理解では、生成されたすべてのIDが0x01000000–0xffffffffの数値スペースを占有しているため、非衝突は保証されますが、これをどこで読んだか思い出せません。
Andrew Wyld

リセットする方法..generateViewId()
reegan29

@kenyeeにはポイントがあり、Viewクラス内で生成されたIDと衝突する可能性があります。私の回答を参照してください:)
2016

else { return View.generateViewId(); }これは、17デバイスよりも小さいAPIレベルの無限ループになりますか?
okarakose 2016年

3

ビューIDフォームAPI 17を動的に生成するには

generateViewId()

での使用に適した値が生成されsetId(int)ます。この値は、aapt forによってビルド時に生成された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);
}

回答の価値を評価するために、将来の訪問者にとってより明確な方法で、適切な説明を回答に追加してください。コードのみの回答は眉をひそめ、レビュー中に削除できます。ありがとう!
ルイス・クルーズ

-1

私の選択:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.