ประกาศองค์ประกอบ UI Android ที่กำหนดเองโดยใช้ XML


473

ฉันจะประกาศองค์ประกอบ UI ของ Android โดยใช้ XML ได้อย่างไร


15
ถ้าใครจะมองหารายการสนับสนุนในตัวรูปแบบแอตทริบิวต์จะสามารถพบได้ที่นี่คือ
Marcin Orlowski

1
บทแนะนำที่ดีสำหรับการเริ่มต้น -> การสร้างมุมมองผสมบน Android
Gayan Weerakutti

คำตอบ:


840

นดรอยด์สำหรับนักพัฒนาคู่มือการมีส่วนที่เรียกว่าอาคารส่วนประกอบที่กำหนดเอง น่าเสียดายที่การอภิปรายเกี่ยวกับคุณสมบัติของ XMLครอบคลุมเฉพาะการประกาศการควบคุมภายในไฟล์เลย์เอาต์และไม่ได้จัดการค่าภายในการเริ่มต้นคลาสจริง ขั้นตอนดังต่อไปนี้:

1. ประกาศคุณลักษณะใน values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="android:text"/>
        <attr name="android:textColor"/>            
        <attr name="extraInformation" format="string" />
    </declare-styleable>
</resources>

สังเกตการใช้ชื่อที่ไม่มีเงื่อนไขในdeclare-styleableแท็ก แอตทริบิวต์ android ที่ไม่ได้มาตรฐานเช่นextraInformationจำเป็นต้องมีการประกาศประเภทของมัน แท็กที่ประกาศในซูเปอร์คลาสจะพร้อมใช้งานในคลาสย่อยโดยไม่ต้องมีการประกาศใหม่

2. สร้างตัวสร้าง

เนื่องจากมีคอนสตรัคเตอร์สองตัวที่ใช้AttributeSetสำหรับการกำหนดค่าเริ่มต้นจึงสะดวกในการสร้างวิธีการกำหนดค่าเริ่มต้นแยกต่างหากสำหรับตัวสร้างเพื่อเรียก

private void init(AttributeSet attrs) { 
    TypedArray a=getContext().obtainStyledAttributes(
         attrs,
         R.styleable.MyCustomView);

    //Use a
    Log.i("test",a.getString(
         R.styleable.MyCustomView_android_text));
    Log.i("test",""+a.getColor(
         R.styleable.MyCustomView_android_textColor, Color.BLACK));
    Log.i("test",a.getString(
         R.styleable.MyCustomView_extraInformation));

    //Don't forget this
    a.recycle();
}

R.styleable.MyCustomViewเป็นint[]ทรัพยากรที่สร้างอัตโนมัติโดยที่องค์ประกอบแต่ละรายการเป็น ID ของแอตทริบิวต์ แอททริบิวถูกสร้างขึ้นสำหรับแต่ละคุณสมบัติใน XML โดยการผนวกชื่อแอ็ตทริบิวต์กับชื่อองค์ประกอบ ตัวอย่างเช่นR.styleable.MyCustomView_android_textมีแอตทริบิวต์สำหรับandroid_text MyCustomViewคุณสมบัตินั้นสามารถดึงจากการTypedArrayใช้getฟังก์ชั่นต่างๆ หากไม่ได้กำหนดแอตทริบิวต์ในที่กำหนดใน XML ดังนั้นnullจะส่งคืน ยกเว้นแน่นอนถ้าประเภทส่งคืนเป็นแบบดั้งเดิมซึ่งในกรณีนี้อาร์กิวเมนต์ที่สองจะถูกส่งกลับ

หากคุณไม่ต้องการที่จะดึงข้อมูลทั้งหมดของคุณลักษณะที่มันเป็นไปได้ที่จะสร้างอาร์เรย์นี้ manually.The ID สำหรับหุ่นยนต์คุณลักษณะมาตรฐานจะรวมอยู่ในขณะแอตทริบิวต์สำหรับโครงการนี้อยู่ในandroid.R.attrR.attr

int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};

โปรดทราบว่าคุณควรจะไม่ได้ใช้อะไรในการandroid.R.styleableตามหัวข้อนี้มันอาจมีการเปลี่ยนแปลงในอนาคต มันยังอยู่ในเอกสารว่าการดูค่าคงที่ทั้งหมดเหล่านี้ในที่เดียวมีประโยชน์

3. ใช้ในรูปแบบไฟล์เช่น layout\main.xml

รวมการประกาศ namespace xmlns:app="http://schemas.android.com/apk/res-auto"ในองค์ประกอบ xml ระดับบนสุด Namespaces จัดเตรียมวิธีการหลีกเลี่ยงความขัดแย้งที่บางครั้งเกิดขึ้นเมื่อ schema ที่แตกต่างกันใช้ชื่อองค์ประกอบเดียวกัน (ดูบทความนี้สำหรับข้อมูลเพิ่มเติม) URL เป็นเพียงรูปแบบของการระบุสกีมาที่ไม่ซ้ำกันจริง ๆ แล้วไม่มีอะไรที่จะต้องโฮสต์ใน URLนั้น หากสิ่งนี้ไม่ปรากฏว่ากำลังทำอะไรอยู่นั่นอาจเป็นเพราะคุณไม่จำเป็นต้องเพิ่มคำนำหน้าเนมสเปซเว้นแต่คุณจะต้องแก้ไขข้อขัดแย้ง

<com.mycompany.projectname.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:text="Test text"
    android:textColor="#FFFFFF"
    app:extraInformation="My extra information"
/> 

อ้างอิงมุมมองที่กำหนดเองโดยใช้ชื่อแบบเต็ม

ตัวอย่าง Android LabelView

หากคุณต้องการตัวอย่างที่สมบูรณ์ให้ดูที่ตัวอย่างมุมมองฉลาก Android

LabelView.java

 TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
 CharSequences=a.getString(R.styleable.LabelView_text);

attrs.xml

<declare-styleable name="LabelView">
    <attr name="text"format="string"/>
    <attr name="textColor"format="color"/>
    <attr name="textSize"format="dimension"/>
</declare-styleable>

custom_view_1.xml

<com.example.android.apis.view.LabelView
    android:background="@drawable/blue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    app:text="Blue" app:textSize="20dp"/>

สิ่งนี้มีอยู่ในLinearLayoutพร้อมกับแอตทริบิวต์ namespace:xmlns:app="http://schemas.android.com/apk/res-auto"

การเชื่อมโยง


14
ฉันต้องการเพิ่มว่าหากองค์ประกอบรูทของคุณต้องการเนมสเปซที่กำหนดเองของคุณคุณจะต้องเพิ่มทั้งเนมสเปซ Android มาตรฐานและของคุณเองหรืออย่างอื่นคุณอาจพบข้อผิดพลาดในการสร้าง
Chase

11
คำตอบนี้เป็นทรัพยากรที่ชัดเจนที่สุดในอินเทอร์เน็ตบนพารามิเตอร์ XML แบบกำหนดเองที่ฉันสามารถหาได้ ขอบคุณ Casebash
Artem Russakovskii

2
ด้วยเหตุผลบางอย่างโปรแกรมแก้ไขภาพปฏิเสธที่จะใช้ค่าข้อความที่เขียนสำหรับ Android: text แต่อุปกรณ์ใช้งานได้ดี มาทำไม
นักพัฒนา android

2
@androiddeveloper ดูเหมือนว่าตัวแก้ไข Eclipse จะปฏิเสธที่จะใช้ค่าสำหรับ android ทั้งหมด: คุณลักษณะ ฉันอยากจะรู้ว่ามันเป็นคุณสมบัติหรือข้อผิดพลาด
deej

4
xmlns มีวัตถุประสงค์อะไร: แอปเนมสเปซและการปรับอัตโนมัติ
IgorGanapolsky

91

การอ้างอิงที่ดี ขอบคุณ! นอกเหนือไปจากมัน

หากคุณมีโครงการห้องสมุดรวมอยู่ด้วยซึ่งได้ประกาศแอตทริบิวต์ที่กำหนดเองสำหรับมุมมองที่กำหนดเองคุณจะต้องประกาศเนมสเปซโครงการของคุณไม่ใช่ของโครงการ เช่น:

ระบุว่าไลบรารีมีแพ็คเกจ "com.example.library.customview" และโครงการทำงานมีแพ็คเกจ "com.example.customview" จากนั้น:

จะไม่ทำงาน (แสดงข้อผิดพลาด "ข้อผิดพลาด: ไม่พบตัวระบุทรัพยากรสำหรับแอตทริบิวต์ 'newAttr' ในแพ็คเกจ 'com.example.library.customview'"):

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.library.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

จะทำงาน:

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

47
สิ่งนี้ได้รับการแก้ไขในตัวอย่าง ADT 17 แล้ว หากต้องการใช้เนมสเปซของแอปจากห้องสมุดxmlns:app="http://schemas.android.com/apk/res-auto"โปรดดูความคิดเห็นที่ 57 ในcode.google.com/p/android/issues/detail?id=9656
nmr

2
เมื่อรวมเนมสเปซที่กำหนดเองของคุณแล้วจะส่งคืนข้อผิดพลาดSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson

เนมสเปซที่กำหนดเองลงท้ายด้วย res-auto เนื่องจากเราใช้ Android Studio และ Gradle มิฉะนั้น (เช่นรุ่น Eclipse บางรุ่น) โดยปกติแล้วจะสิ้นสุดใน lib / [ชื่อแพ็คเกจของคุณ]
Universe

เนมสเปซที่กำหนดเองสิ้นสุดลงres-autoเนื่องจากเรากำลังใช้ Android Studio และ Gradle มิฉะนั้น (เช่นบางรุ่น Eclipse) lib/[your package name]ก็มักจะจบลงใน iehttp://schemas.android.com/apk/lib/[your package name]
จักรวาล

27

นอกเหนือจากคำตอบที่โหวตมากที่สุด

obtainStyledAttributes ()

ฉันต้องการเพิ่มคำบางคำเกี่ยวกับการใช้งาน getStyledAttributes () เมื่อเราสร้างมุมมองที่กำหนดเองโดยใช้แอตทริบิวต์ android: xxx prdefined โดยเฉพาะอย่างยิ่งเมื่อเราใช้ TextAppearance
ตามที่กล่าวไว้ใน "2. การสร้างตัวสร้าง" มุมมองที่กำหนดเองจะได้รับ AttributeSet ในการสร้าง การใช้งานหลักที่เราเห็นในซอร์สโค้ดของ TextView (API 16)

final Resources.Theme theme = context.getTheme();

// TextAppearance is inspected first, but let observe it later

TypedArray a = theme.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

int n = a.getIndexCount();
for (int i = 0; i < n; i++) 
{
    int attr = a.getIndex(i);
    // huge switch with pattern value=a.getXXX(attr) <=> a.getXXX(a.getIndex(i))
}
a.recycle();

เราเห็นอะไรที่นี่?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
ชุดคุณสมบัติจะถูกประมวลผลโดยธีมตามเอกสาร ค่าคุณสมบัติเป็นขั้นตอนที่รวบรวมโดยขั้นตอน แอ็ตทริบิวต์แรกถูกเติมจากธีมจากนั้นค่าจะถูกแทนที่ด้วยค่าจากสไตล์และค่าที่แน่นอนที่สุดจาก XML สำหรับอินสแตนซ์มุมมองพิเศษจะแทนที่ผู้อื่น
อาร์เรย์ของแอตทริบิวต์ที่ร้องขอ - com.android.internal.R.styleable.TextView
มันเป็นอาร์เรย์คงที่ปกติ หากเราร้องขอคุณสมบัติมาตรฐานเราสามารถสร้างอาร์เรย์นี้ได้ด้วยตนเอง

สิ่งที่ไม่ได้กล่าวถึงในเอกสารประกอบ - ลำดับของผลลัพธ์องค์ประกอบ TypedArray
เมื่อประกาศมุมมองที่กำหนดเองใน attrs.xml ค่าคงที่พิเศษสำหรับดัชนีแอตทริบิวต์จะถูกสร้างขึ้น และเราสามารถดึงค่าด้วยวิธีนี้: a.getString(R.styleable.MyCustomView_android_text). แต่สำหรับคู่มือint[]ไม่มีค่าคงที่ ฉันคิดว่า getXXXValue (arrayIndex) จะทำงานได้ดี

และคำถามอื่นคือ: "เราจะเปลี่ยนค่าคงที่ภายในและขอคุณลักษณะมาตรฐานได้อย่างไร" เราสามารถใช้ค่า android.R.attr. *

ดังนั้นหากเราต้องการใช้แอตทริบิวต์ TextAppearance มาตรฐานในมุมมองที่กำหนดเองและอ่านค่าใน Constructor เราสามารถแก้ไขโค้ดจาก TextView ด้วยวิธีนี้:

ColorStateList textColorApp = null;
int textSize = 15;
int typefaceIndex = -1;
int styleIndex = -1;

Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomLabel, defStyle, 0);
TypedArray appearance = null;
int apResourceId = a.getResourceId(R.styleable.CustomLabel_android_textAppearance, -1);
a.recycle();
if (apResourceId != -1)
{
    appearance = 
        theme.obtainStyledAttributes(apResourceId, new int[] { android.R.attr.textColor, android.R.attr.textSize, 
            android.R.attr.typeface, android.R.attr.textStyle });
}
if (appearance != null)
{
    textColorApp = appearance.getColorStateList(0);
    textSize = appearance.getDimensionPixelSize(1, textSize);
    typefaceIndex = appearance.getInt(2, -1);
    styleIndex = appearance.getInt(3, -1);

    appearance.recycle();
}

ตำแหน่งที่ CustomLabel กำหนดไว้:

<declare-styleable name="CustomLabel">
    <!-- Label text. -->
    <attr name="android:text" />
    <!-- Label text color. -->
    <attr name="android:textColor" />
    <!-- Combined text appearance properties. -->
    <attr name="android:textAppearance" />
</declare-styleable>

อาจเป็นเพราะฉันเข้าใจผิดบางอย่าง แต่เอกสาร Android เกี่ยวกับ getStyledAttributes () นั้นแย่มาก

การขยายองค์ประกอบ UI มาตรฐาน

ในขณะเดียวกันเราก็สามารถขยายองค์ประกอบ UI มาตรฐานโดยใช้คุณสมบัติที่ประกาศไว้ทั้งหมด วิธีการนี้ไม่ดีนักเนื่องจาก TextView เช่นประกาศคุณสมบัติมากมาย และมันจะเป็นไปไม่ได้ที่จะใช้ฟังก์ชั่นเต็มรูปแบบใน overriden onMeasure () และ onDraw ()

แต่เราสามารถเสียสละองค์ประกอบที่กำหนดเองในเชิงทฤษฎีได้ พูดว่า "ฉันรู้แน่ชัดว่าฟีเจอร์ใดที่ฉันจะใช้" และไม่แชร์รหัสกับใคร

CustomComponent(Context, AttributeSet, defStyle)แล้วเราสามารถใช้คอนสตรัค หลังจากการโทรsuper(...)เราจะมีการแยกวิเคราะห์คุณลักษณะและให้บริการผ่านวิธีการทะเยอทะยาน


android: xxx คุณสมบัติที่กำหนดไว้ล่วงหน้าทำงานใน eclipse gui designer หรือไม่
deej

คุณลักษณะดังกล่าวได้รับการยอมรับโดยปลั๊กอิน Eclipse ADT ในตัวแก้ไขคุณสมบัติ ฉันสามารถดูค่าเริ่มต้นจากสไตล์ของฉันหากค่าบางอย่างไม่ได้กำหนดไว้ และอย่าลืมเพิ่มคำอธิบายประกอบ @RemoteView ในชั้นเรียนของคุณ
yuriy.weiss

ไม่สามารถทำงานได้ Eclipse ทำการโหลดค่า null สำหรับ getText และส่ง android.content.res.Resources $ NotFoundException สำหรับ getResourceId แม้ว่าแอปจะทำงานได้ดีบนอุปกรณ์
deej

ขอโทษฉันไม่สามารถช่วยคุณได้ ฉันได้สร้างโครงการตัวอย่างเพื่อทดสอบความเป็นไปได้เท่านั้นและไม่พบข้อผิดพลาดดังกล่าว
yuriy.weiss

นี่คือสิ่งที่ดีกว่าการทำแผนที่แอตทริบิวต์ที่กำหนดเองของมุมมองที่กำหนดเองไปยังแอตทริบิวต์ในตัวของมุมมองในตัวที่มีอยู่ภายใน
samis

13

ดูเหมือนว่า Google ได้อัปเดตหน้าผู้พัฒนาและเพิ่มการฝึกอบรมที่นั่น

หนึ่งในนั้นเกี่ยวข้องกับการสร้างมุมมองที่กำหนดเองและสามารถดูได้ที่นี่


5

ขอบคุณมากสำหรับคำตอบแรก

สำหรับฉันฉันมีปัญหาเดียวกับมัน เมื่อขยายมุมมองของฉันฉันมีข้อผิดพลาด: java.lang.NoSuchMethodException: MyView (บริบทคุณลักษณะ)

ฉันแก้ไขมันโดยการสร้างนวกรรมิกใหม่:

public MyView(Context context, AttributeSet attrs) {
     super(context, attrs);
     // some code
}

หวังว่านี่จะช่วยได้!


0

คุณสามารถรวมไฟล์เลย์เอาต์ใด ๆ ในไฟล์เลย์เอาต์อื่น ๆ เป็น -

             <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="30dp" >

                <include
                    android:id="@+id/frnd_img_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_imagefile"/>

                <include
                    android:id="@+id/frnd_video_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_video_lay" />

                <ImageView
                    android:id="@+id/downloadbtn"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/plus"/>
            </RelativeLayout>

นี่คือไฟล์เลย์เอาต์ในแท็กรวมเป็นไฟล์เลย์เอาต์. xml อื่น ๆ ในโฟลเดอร์ res เดียวกัน


ฉันได้ลองแล้วปัญหาที่ฉันมีก็คือเลย์เอาต์ที่รวมมานั้นไม่สามารถ 'ดัดแปลง' ไม่สามารถสร้างข้อมูลทั่วไปได้ ตัวอย่างเช่นเมื่อฉันรวมปุ่มในลักษณะที่คล้ายกันถ้าฉันพยายามตั้งค่าข้อความใน xml มันจะทำงาน
cfl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.