Android:视图。setID(int id)编程-如何避免id冲突?

我在for循环中以编程方式添加TextViews,并将它们添加到数组列表中。

如何使用TextView.setId(int id)?我要用什么样的整数ID才能不与其他ID冲突?

287628 次浏览

根据View文档

标识符在这个视图的层次结构中不必是唯一的。标识符应该是一个正数。

你可以使用任何你喜欢的正整数,但在这种情况下,可以有一些具有等效id的视图。如果你想在层次结构中搜索某个视图,调用带有一些关键对象的setTag可能很方便。

你也可以在res/values中定义ids.xml。你可以在android的示例代码中看到一个确切的例子。

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

这对我来说很管用:

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

(这是对业余爱好者的回答的评论,但它太长了…呵呵)

当然,这里不需要静电。你可以使用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

编辑/修复:刚刚意识到我完全搞砸了保存。我一定是喝醉了。

从API 17开始,View类有一个静态方法 generateViewId()

生成适合setId(int)的值

从API级别17及以上,你可以调用:View.generateViewId ()

然后使用View.setId (int)

如果你的应用程序的目标低于API级别17,使用ViewCompat.generateViewId ()

为了动态生成视图Id表单API 17使用

generateViewId()

它将生成一个适合在setId(int)中使用的值。此值不会与aapt在构建时为R.id生成的ID值冲突。

只是对@phantomlimb的答案的补充,

View.generateViewId()要求API级别>= 17,
此工具与所有API兼容 根据当前API级别,< p>,
它决定天气是否使用系统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();
}


}
}

您可以使用xml资源文件定义稍后将在R.id类中使用的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);
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();
}
}
}
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);
}

我使用:

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

通过使用随机数,我总是有很大的机会在第一次尝试中获得唯一的id。

我的选择:

// Method that could us an unique id


int getUniqueId(){
return (int)
SystemClock.currentThreadTimeMillis();
}

Compat库现在也支持API级别之前的generateViewId()方法。

只要确保使用Compat库的一个版本,即27.1.0+

例如,在你的build.gradle文件中,放入:

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

然后你可以简单地使用ViewCompat类中的generateViewId()而不是View类,如下所示:

//将分配唯一的ID myView。id = ViewCompat.generateViewId() < /代码> < / p >

编码愉快!

受到@dilettante answer的启发,下面是我在kotlin中作为扩展函数的解决方案:

/* sets a valid id that isn't in use */
fun View.findAndSetFirstValidId() {
var i: Int
do {
i = Random.nextInt()
} while (findViewById<View>(i) != null)
id = i
}