Android: View.setID (int id) โดยทางโปรแกรม - วิธีหลีกเลี่ยงข้อขัดแย้งของ ID?


335

ฉันเพิ่ม TextViews โดยทางโปรแกรมใน for-loop และเพิ่มลงใน ArrayList

ฉันจะใช้งานTextView.setId(int id)อย่างไร ฉันควรใช้ Integer ID ใดดังนั้นจึงไม่ขัดแย้งกับรหัสอื่น

คำตอบ:


147

ตามViewเอกสารประกอบ

ตัวระบุไม่จำเป็นต้องไม่ซ้ำกันในลำดับชั้นของมุมมองนี้ ตัวระบุควรเป็นจำนวนบวก

ดังนั้นคุณสามารถใช้จำนวนเต็มบวกใด ๆ ที่คุณชอบ แต่ในกรณีนี้อาจมีบางมุมมองที่มี id เทียบเท่า หากคุณต้องการค้นหามุมมองบางอย่างในลำดับชั้นที่เรียกsetTagด้วยวัตถุสำคัญบางอย่างอาจมีประโยชน์


2
น่าสนใจฉันไม่ทราบว่ารหัสต้องไม่ซ้ำกันหรือไม่ ถ้าอย่างfindViewByIdนั้นก็รับประกันว่ามุมมองไหนจะถูกส่งคืนถ้ามีมากกว่าหนึ่งอันที่มี ID เดียวกัน? เอกสารไม่ได้พูดถึงอะไรเลย
Matthias

26
ฉันคิดว่าเอกสารพูดถึงบางสิ่งเกี่ยวกับเรื่องนี้ หากคุณมีมุมมองที่มี ID เดียวกันในลำดับชั้นเดียวกันก็findViewByIdจะกลับมาเป็นครั้งแรกที่พบ
kaneda

2
@ DanyY ฉันไม่แน่ใจว่าถ้าฉันเข้าใจถูกต้องว่าคุณหมายถึงอะไร สิ่งที่ฉันพยายามจะพูดคือถ้าหากเลย์เอาต์ที่คุณตั้งไว้setContentView()มี 10 วิวด้วย id ที่ตั้งไปยังหมายเลขไอดีเดียวกันในลำดับชั้นเดียวกันการเรียกใช้findViewById([repeated_id])จะส่งคืนมุมมองแรกที่ตั้งค่าด้วยไอดีซ้ำ ๆ นั่นคือสิ่งที่ฉันหมายถึง.
kaneda

51
-1 ฉันไม่เห็นด้วยกับคำตอบนี้เพราะ onSaveInstanceState และ onRestoreInstanceState จำเป็นต้องมี id ที่ไม่ซ้ำกันเพื่อให้สามารถบันทึก / กู้คืนสถานะของลำดับชั้นมุมมอง หากสองมุมมองมี ID เดียวกันสถานะของหนึ่งในนั้นจะหายไป ดังนั้นหากคุณไม่บันทึกสถานะดูว่าตัวคุณเองมีรหัสที่ซ้ำกันทั้งหมดจะไม่เป็นความคิดที่ดี
Emanuel Moecklin

3
Id ต้องไม่ซ้ำกัน เริ่มต้นจากAPI ระดับ 17มีวิธีการคงที่ในคลาสมุมมองที่สร้างรหัสสุ่มเพื่อใช้เป็นรหัสมุมมอง วิธีการดังกล่าวรับรองว่ารหัสที่สร้างขึ้นจะไม่ชนกับรหัสมุมมองอื่น ๆ ที่สร้างขึ้นโดยเครื่องมือ aapt ในระหว่างเวลาสร้าง developer.android.com/reference/android/view/…
Mahmoud

576

จาก API ระดับ 17 ขึ้นไปคุณสามารถโทร: View.generateViewId ()

จากนั้นใช้View.setId (int)

หากแอปของคุณกำหนดเป้าหมายต่ำกว่า API ระดับ 17 ให้ใช้ViewCompat.generateViewId ()


2
ฉันใส่ไว้ในซอร์สโค้ดของฉันเพราะเราต้องการสนับสนุนระดับ API ที่ต่ำกว่า มันใช้งานได้ แต่การวนซ้ำไม่สิ้นสุดไม่ใช่วิธีปฏิบัติที่ดี
SXC

5
@SimonXinCheng Infinite loops เป็นรูปแบบทั่วไปที่ใช้ในอัลกอริทึมที่ไม่บล็อก ตัวอย่างเช่นมีการดูAtomicIntegerวิธีการใช้งาน
ไอดอล

7
ใช้งานได้ดี! หมายเหตุหนึ่ง: จากการทดลองของฉันคุณต้องเรียก setId () ก่อนที่คุณจะเพิ่มมุมมองในเค้าโครงที่มีอยู่มิฉะนั้น OnClickListener จะทำงานไม่ถูกต้อง
ลุค

4
ขอบคุณคุณจะเล็กเกินไป แต่ขอบคุณ คำถามคือสิ่งที่for(;;)ฉันไม่เคยเห็นมาก่อน เรียกว่าอะไร
ผู้รุกราน

5
@Aggressor: มันเป็นวง 'for' ที่ว่างเปล่า
sid_09

143

คุณสามารถตั้งค่า 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
สิ่งนี้ไม่ทำงานเมื่อฉันมีองค์ประกอบที่ไม่รู้จักจำนวนมากฉันจะกำหนดรหัสให้
Mooing Duck

1
@MooingDuck ฉันรู้ว่านี่เป็นเวลาหนึ่งปีแล้ว แต่เมื่อฉันต้องกำหนด Ids เฉพาะตอนรันไทม์ด้วยจำนวนองค์ประกอบที่ไม่รู้จักฉันก็ใช้"int currentId = 1000; whateverView.setId(currentId++);- เพิ่ม ID นั้นทุกครั้งที่currentId++ใช้รับรองรหัสที่ไม่ซ้ำกันและฉันสามารถเก็บ ID ใน ArrayList ของฉันเพื่อการเข้าถึงในภายหลัง
Mike ใน SAT

3
@MikeinSAT: เพียงรับรองได้ว่าพวกเขาจะไม่ซ้ำกันในหมู่พวกเขา ไม่ได้ทำให้มัน "ดังนั้นจึงไม่ขัดแย้งกับรหัสอื่น ๆ " ซึ่งเป็นส่วนสำคัญของคำถาม
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

3
นอกจากนี้อาจไม่ช้าสำหรับเค้าโครงที่ซับซ้อนหรือไม่
Daniel Rodriguez

15
findViewById()เป็นการทำงานที่ช้า วิธีการทำงาน แต่ค่าใช้จ่ายของประสิทธิภาพ
Kiril Aleksandrov

10

(นี่เป็นความคิดเห็นต่อคำตอบของ dilettante แต่มันยาวเกินไป ... hehe)

แน่นอนว่าคงไม่จำเป็นต้องใช้ที่นี่ คุณสามารถใช้ SharedPreferences เพื่อบันทึกแทนสแตติก ไม่ว่าจะด้วยวิธีใดเหตุผลคือบันทึกความคืบหน้าปัจจุบันเพื่อไม่ให้ช้าเกินไปสำหรับเค้าโครงที่ซับซ้อน เพราะในความเป็นจริงหลังจากการใช้เพียงครั้งเดียวมันจะค่อนข้างเร็วในภายหลัง อย่างไรก็ตามฉันไม่รู้สึกว่านี่เป็นวิธีที่ดีในการทำเพราะถ้าคุณต้องสร้างหน้าจอใหม่อีกครั้ง (พูดว่าonCreateถูกเรียกอีกครั้ง) คุณอาจต้องการเริ่มต้นใหม่ตั้งแต่ต้นอย่างไรก็ตามไม่จำเป็นต้องใช้สแตติก ดังนั้นเพียงแค่ทำให้มันเป็นตัวแปรอินสแตนซ์แทนที่จะเป็นแบบคงที่

นี่คือรุ่นที่เล็กกว่าซึ่งทำงานได้เร็วกว่าเล็กน้อยและอ่านง่ายกว่า:

int fID = 0;

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

ฟังก์ชั่นด้านบนนี้ควรจะเพียงพอ เพราะเท่าที่ฉันสามารถบอกได้รหัสที่สร้างโดย Android นั้นอยู่ในพันล้านดังนั้นนี่อาจจะกลับมา1เป็นครั้งแรกและจะค่อนข้างเร็ว เพราะมันจะไม่วนลูปผ่าน 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 ที่ใช้ android: https://stackoverflow.com/a/13241629/693927

แก้ไข / แก้ไข: เพิ่งรู้ว่าฉันโง่ไปหมดบันทึก ฉันต้องเมา


1
นี่ควรเป็นคำตอบที่ดีที่สุด ใช้ประโยชน์ได้ดีกับคำหลัก ++ และข้อความว่างเปล่า;)
Aaron Gillion

9

ขณะนี้ไลบรารี 'Compat' ยังรองรับgenerateViewId()วิธีการสำหรับระดับ API ก่อนหน้า 17

เพียงตรวจสอบให้แน่ใจว่าใช้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

ความเข้าใจของฉันคือรหัสที่สร้างขึ้นทั้งหมดใช้พื้นที่จำนวน 0x01000000–0xffffffff ดังนั้นคุณจึงรับประกันได้ว่าจะไม่ขัดแย้ง แต่ฉันจำไม่ได้ว่าฉันอ่านตรงไหน
Andrew Wyld

วิธีการรีเซ็ต ..generateViewId()
reegan29

@kenyee มีจุดก็สามารถชนกับรหัสที่สร้างขึ้นในชั้นดู ดูคำตอบของฉัน :)
ร้องเพลง

else { return View.generateViewId(); }สิ่งนี้จะวนซ้ำไม่สิ้นสุดสำหรับระดับ api ที่เล็กกว่า 17 อุปกรณ์หรือไม่
okarakose

3

เพื่อสร้างการใช้แบบฟอร์ม View Id API 17

generateViewId ()

setId(int)ซึ่งจะสร้างความคุ้มค่าเหมาะสำหรับใช้ใน ค่านี้จะไม่ชนกับค่า ID ที่สร้างเวลาที่สร้างโดย aapt 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;
}

การใช้หมายเลขสุ่มทำให้ฉันมีโอกาสสูงที่จะได้รับรหัสเฉพาะในครั้งแรก


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.