Android:以编程方式设置View.setID(int id)-如何避免ID冲突?


334

我在一个for循环中以编程方式添加TextViews并将它们添加到ArrayList中。

如何使用TextView.setId(int id)?我想出什么整数ID,以免与其他ID冲突?

Answers:


146

根据View文件

标识符在此视图的层次结构中不必唯一。标识符应为正数。

因此,您可以使用任何喜欢的正整数,但是在这种情况下,可能会有一些具有相同ID的视图。如果要在层次结构中搜索某些视图,则可以setTag使用一些关键对象进行调用。


2
有趣的是,我不知道ID是否需要唯一?那么,findViewById如果有多个具有相同ID的视图返回,那么是否可以保证返回哪个视图呢?该文档没有提及任何内容。
马赛厄斯

26
我认为文档中对此有所提及。如果在同一层次结构中具有相同ID的视图,findViewById则将返回它找到的第一个视图。
kaneda 2012年

2
@DanyY我不确定我是否正确理解您的意思。我想说的是,如果您设置的布局setContentView()有10个视图,其ID设置为同一层次结构中的相同ID号,则调用findViewById([repeated_id])将返回具有该重复ID的第一个视图集。我正是这个意思。
kaneda 2013年

51
-1我不同意这个答案,因为onSaveInstanceState和onRestoreInstanceState需要唯一的ID才能保存/恢复视图层次结构的状态。如果两个视图具有相同的ID,则其中一个视图的状态将丢失。因此,除非您保存View状态,否则所有拥有重复ID的人都不是一个好主意。
伊曼纽尔·莫克林

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 2013年

5
@SimonXinCheng无限循环是非阻塞算法中常用的模式。例如看一下AtomicInteger方法的实现。
Idolon

7
很棒!注意事项:根据我的实验,在将视图添加到现有布局之前,必须调用setId(),否则OnClickListener将无法正常工作。
路加福音

4
谢谢你太小了,但谢谢你。问题,for(;;)我以前从未见过什么。那叫什么
侵略者2015年

5
@Aggressor:它是一个空的“ for”循环。
sid_09,2016年

142

您可以设置ID,以便稍后在R.id课堂上使用xml资源文件使用,并让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我知道这已经晚了一年,但是当我必须在运行时分配具有未知数量元素的唯一ID时,我只需使用"int currentId = 1000; whateverView.setId(currentId++);-每次currentId++使用时都会增加ID ,从而确保唯一ID,并且我可以存储我的ArrayList中的ID,以供以后访问。
迈克(Mike)在SAT

3
@MikeinSAT:那只能保证它们之间是唯一的。但这并不意味着“它不会与其他ID冲突”,这是问题的关键部分。
Mooing Duck

1
这是一个成功的答案,因为其他人正在给Android 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


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()操作缓慢。该方法有效,但以性能为代价。
Kiril Aleksandrov 2014年

10

(这是对dilettante的回答的评论,但时间太长了……呵呵)

当然,这里不需要静态的。您可以使用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
这应该是最佳答案。大量使用++关键字和空语句;)
Aaron Gillion 2015年

9

现在,“兼容”库还支持generateViewId()17级之前的API级别的方法。

只要确保使用的Compat库版本是27.1.0+

例如,在您的build.gradle文件中,输入:

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

然后,您可以简单地使用generateViewId()from 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 2015年

如何重置..generateViewId()
reegan29 2015年

@kenyee有一点,它可以与View类中生成的ID发生冲突。见我的回答:)
2016年

else { return View.generateViewId(); }对于小于17个设备的api级别,这将进入无限循环?
okarakose

3

为了动态生成View 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();    
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.