编程视图如何设置唯一ID?

37

我正在我的应用程序中创建大量编程视图View。 实际上,它们默认全部具有相同的id=-1。 为了使用它们,我需要生成唯一的id。

我尝试了几种方法-基于随机数生成和基于当前时间,但是无论如何都不能保证不同的视图将具有不同的id。

只是想知道是否有更可靠的方法来生成唯一的id? 可能有特殊的方法/类吗?


我认为最好的方法是保持一个整型计数器,每添加一个视图就将其递增并将其设置为ID。 - ingsaurabh
3
可能是Android:View.setID(int id)以编程方式 - 如何避免ID冲突?的重复问题。 - Ciro Santilli OurBigBook.com
5个回答

56

仅想补充Kaj的回答,从API 17级别开始,您可以调用

View.generateViewId()

然后使用View.setId(int)方法。

如果您需要将其用于低于17级别的目标,请在此处查看View.java中的内部实现,您可以直接在项目中使用:

private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

/**
 * Generate a value suitable for use in {@link #setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    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;
        }
    }
}

0x00FFFFFF以上的ID号被保留用于在/res xml文件中定义的静态视图。(很可能是来自我的项目中R.java的0x7f******)

从代码上看,Android不希望您使用0作为视图的ID,并且需要将其翻转以避免与静态资源ID发生冲突,翻转后应该在0x01000000之前。


它不希望您使用0作为视图ID,因为这是通用的表示无效资源ID的方式,在运行时查询资源时会出现问题。 - Jared

49

这是对@phantomlimb的回答的补充,

View.generateViewId()需要API Level >=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();
        }

    }
}

2
实际上,这是不正确的 - 您使用了一个不同的AtomicInteger计数器而不是View.generateViewId(),因此您不能像您在类的文档中所断言的那样可以互换使用此类和View.generateViewId()。需要完全使用此类。 - iainmcgin
4
如果你可以交替使用ViewIdGenerator.generateViewId()View.generateViewId(),那就意味着当前的Android版本(Build.VERSION.SDK_INT)大于等于17。因此,代码中的if (Build.VERSION.SDK_INT < 17) {…}将永远不会执行。实际上,在这种情况下它总是使用View.generateViewId()。 :) - fantouch
哈,很高兴在代码中看到一些中文注释 :) - X.Y.
@reegan29 由于Android官方API View.generateViewId()没有reset,因此这段代码片段也没有。 - fantouch
源代码在这里:http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/4.4_r1-robolectric-1/android/view/View.java#18048。 - Jing Li

26

12

创建一个单例类,其中包含一个原子整数。增加整数并在需要视图ID时返回该值。

ID将在进程执行期间保持唯一,但在重新启动进程时将重置。

public class ViewId {

    private static ViewId INSTANCE = new ViewId();

    private AtomicInteger seq;

    private ViewId() {
        seq = new AtomicInteger(0);
    }

    public int getUniqueId() {
        return seq.incrementAndGet();
    }

    public static ViewId getInstance() {
        return INSTANCE;
    }
}

请注意,如果视图“graph”中已经有使用id的视图,则id可能不唯一。 您可以尝试从Integer.MAX_VALUE开始减少而不是从1-> MAX_VALUE。


1
除了检查R.java文件中生成的每个ID之外,似乎没有其他方法可以确定一个ID是否唯一(据我所知)。在某些情况下,由于ID冲突,Android会出现问题。我发现最好的方法是使用Integer.MAX_VALUE,并在每次需要新ID时减少该值,就像你提到的那样。 - infl3x
7
这是一种方法...但是正如杨晓超所提到的,在API 17及以上版本中,你可以使用View.generateViewId()方法。 - lory105
你能回答我吗?这基于你的回答... https://dev59.com/1o7ea4cB1Zd3GeqPFcF3 - reegan29
1
你能否再详细解释一下,在哪种情况下这个答案产生的ID不是唯一的? - Giorgi Moniava

3

关于API<17的后备方案,我看到建议的解决方案开始从0或1生成ID。View类还有另一个实例的生成器,并且也从数字1开始计数,这将导致您和View的生成器生成相同的ID,您最终将在您的视图层次结构中拥有具有相同ID的不同视图。不幸的是,目前没有很好的解决方案,但这是一个应该被充分记录的黑客手段:

public class AndroidUtils {

/**
 *  Unique view id generator, like the one used in {@link View} class for view id generation.
 *  Since we can't access the generator within the {@link View} class before API 17, we create
 *  the same generator here. This creates a problem of two generator instances not knowing about
 *  each other, and we need to take care that one does not generate the id already generated by other one.
 *
 *  We know that all integers higher than 16 777 215 are reserved for aapt-generated identifiers
 *  (source: {@link View#generateViewId()}, so we make sure to never generate a value that big.
 *  We also know that generator within the {@link View} class starts at 1.
 *  We set our generator to start counting at 15 000 000. This gives us enough space
 *  (15 000 000 - 16 777 215), while making sure that generated IDs are unique, unless View generates
 *  more than 15M IDs, which should never happen.
 */
private static final AtomicInteger viewIdGenerator = new AtomicInteger(15000000);

/**
 * Generate a value suitable for use in {@link View#setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return generateUniqueViewId();
    } else {
        return View.generateViewId();
    }
}

private static int generateUniqueViewId() {
    while (true) {
        final int result = viewIdGenerator.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 (viewIdGenerator.compareAndSet(result, newValue)) {
            return result;
        }
    }
}

}

关于应用程序自定义生成器和View类内部生成器之间可能存在冲突的观点很好。您是否检查了API 16中View.generateViewId()的实际逻辑?我没有找到源代码的副本,所以只希望它基本上与更近期的API版本相同。 - jk7
还没有找到源代码。但是即使在新的API上使用View.generateViewId()时,我也遇到了冲突,所以我修改了代码,始终使用自己的generateUniqueViewId方法。 - Singed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接