การกำหนด attrs ที่กำหนดเอง


472

ฉันต้องใช้คุณลักษณะของตัวเองเช่น com.android.R.attr

ไม่พบอะไรในเอกสารอย่างเป็นทางการดังนั้นฉันต้องการข้อมูลเกี่ยวกับวิธีกำหนด Attrs เหล่านี้และวิธีใช้จากรหัสของฉัน


20
เอกสารเหล่านี้อาจใหม่กว่าที่โพสต์ของคุณ แต่เพื่อให้เป็นปัจจุบันคุณสามารถหาเอกสารที่เป็นทางการสำหรับแอตทริบิวต์ที่ดีได้ที่นี่: developer.android.com/training/custom-views/…
OYRM

ฉันขอแนะนำบทความดี ๆ พร้อมตัวอย่างเกี่ยวกับแอตทริบิวต์ที่กำหนดเอง: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński

ตัวอย่างการทำงานขนาดเล็กอาจมีประโยชน์: github.com/yujiaao/MergeLayout1
Yu Jiaao

คำตอบ:


971

ปัจจุบันเอกสารที่ดีที่สุดคือแหล่งที่มา คุณสามารถใช้ดูที่นี่ (attrs.xml)

คุณสามารถกำหนดคุณสมบัติใน<resources>องค์ประกอบด้านบนหรือด้านในของ<declare-styleable>องค์ประกอบ ถ้าฉันจะใช้ attr มากกว่าหนึ่งแห่งฉันจะใส่ไว้ในองค์ประกอบหลัก หมายเหตุแอ็ตทริบิวต์ทั้งหมดใช้เนมสเปซส่วนกลางเดียวกัน นั่นหมายความว่าแม้ว่าคุณจะสร้างแอททริบิวใหม่ภายใน<declare-styleable>องค์ประกอบก็สามารถใช้งานได้ภายนอกและคุณไม่สามารถสร้างแอททริบิวอื่นด้วยชื่อเดียวกันกับประเภทอื่น

<attr>องค์ประกอบมีแอตทริบิวต์ XML สองและname ช่วยให้คุณสามารถเรียกมันว่าบางสิ่งบางอย่างและนี่คือวิธีการที่คุณจะจบลงหมายถึงว่ามันในรหัสเช่น แอททริบิวอาจมีค่าแตกต่างกันไปขึ้นอยู่กับ 'ประเภท' ของแอททริบิวที่คุณต้องการformatnameR.attr.my_attributeformat

  • การอ้างอิง - หากเป็นการอ้างอิงถึงรหัสทรัพยากรอื่น (เช่น "@ color / my_color", "@ layout / my_layout")
  • สี
  • บูล
  • มิติ
  • ลอย
  • จำนวนเต็ม
  • เชือก
  • เศษ
  • enum - โดยปกติจะกำหนดไว้โดยปริยาย
  • ธง - โดยปกติจะกำหนดไว้โดยปริยาย

คุณสามารถตั้งค่ารูปแบบการหลายประเภทโดยใช้เช่น|format="reference|color"

enum คุณสมบัติสามารถกำหนดได้ดังนี้

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flag แอ็ตทริบิวต์คล้ายกันยกเว้นค่าที่ต้องกำหนดเพื่อให้สามารถบิตหรือรวมเข้าด้วยกัน:

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

นอกจากนี้ยังมี<declare-styleable>องค์ประกอบเป็นองค์ประกอบ สิ่งนี้อนุญาตให้คุณกำหนดคุณลักษณะที่มุมมองที่กำหนดเองสามารถใช้ได้ คุณทำเช่นนี้โดยการระบุองค์ประกอบถ้ามันถูกกำหนดไว้ก่อนหน้านี้ที่คุณไม่ได้ระบุ<attr> formatหากคุณต้องการใช้ android attr ซ้ำเช่น android: แรงโน้มถ่วงคุณสามารถทำได้nameดังนี้

ตัวอย่างของมุมมองที่กำหนดเอง<declare-styleable>:

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

เมื่อกำหนดแอตทริบิวต์ที่กำหนดเองของคุณใน XML ในมุมมองที่กำหนดเองคุณต้องทำบางสิ่ง ก่อนอื่นคุณต้องประกาศเนมสเปซเพื่อค้นหาแอตทริบิวต์ของคุณ คุณทำสิ่งนี้กับองค์ประกอบเค้าโครงรูท xmlns:android="http://schemas.android.com/apk/res/android"โดยปกติมีเพียง xmlns:whatever="http://schemas.android.com/apk/res-auto"คุณต้องตอนนี้ยังเพิ่ม

ตัวอย่าง:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:whatever="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <org.example.mypackage.MyCustomView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

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

public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

  String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

  //do something with str

  a.recycle();
}

ตอนจบ. :)


14
นี่คือตัวอย่างโครงการที่แสดงแอตทริบิวต์ที่กำหนดเองเพื่อใช้กับแบบกำหนดเองView: github.com/commonsguy/cw-advandroid/tree/master/Views/
......

7
หากคุณใช้ Attrs ที่กำหนดเองจากโครงการห้องสมุด: ดูคำถามนี้: stackoverflow.com/questions/5819369// - ดูเหมือนว่าจะใช้งานได้ถ้าคุณใช้xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- ไม่มีการคัดลอก attrs.xml โปรดทราบว่าเส้นทาง URI ของเนมสเปซต้องเป็น / apk / * lib * not / apk / res
thom_nic

2
@ThomNichols apk/libเคล็ดลับใช้ไม่ได้กับแอตทริบิวต์ที่กำหนดเองพร้อมรูปแบบการอ้างอิงจากโครงการห้องสมุด สิ่งที่ได้ผลคือการใช้งานapk/res-autoตามที่แนะนำในstackoverflow.com/a/13420366/22904ด้านล่างและในstackoverflow.com/a/10217752
Giulio Piancastelli

1
Quoting @Qberticus: "แอตทริบิวต์ของแฟล็กคล้ายกันยกเว้นค่าที่ต้องกำหนดเพื่อให้สามารถบิตหรือรวมเข้าด้วยกัน" ในความคิดของฉันนี่คือการทำความเข้าใจความแตกต่างที่สำคัญระหว่างenumและflag: อดีตให้เราเลือกหนึ่งและหนึ่งค่าเดียวหลังให้เรารวมหลาย ฉันเขียนคำตอบที่ยาวขึ้นในคำถามที่คล้ายกันที่นี่และหลังจากพบคำถามนี้ฉันคิดว่าฉันจะเชื่อมโยงกับที่
ราด Haring

5
a.recycle()เป็นสิ่งสำคัญมากที่นี่เพื่อเพิ่มหน่วยความจำ
Tash Pemhiwa

87

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

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

ด้วย:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

มิฉะนั้นแอปพลิเคชันที่ใช้ไลบรารีจะมีข้อผิดพลาดรันไทม์


3
นี่เพิ่งเพิ่มเข้ามา ... ฉันคิดว่าภายในไม่กี่สัปดาห์ที่ผ่านมา แน่นอนว่ามันเพิ่มเข้ามานานหลังจากที่ Qberticus เขียนคำตอบของเขา
ArtOfWarfare

12
ฉันคิดว่ามันเก่ากว่านั้น แต่มันก็เพิ่มขึ้นอย่างแน่นอนหลังจาก Qberticus เขียนคำตอบของเขา ไม่ผิดกับเขาเลยเพียงแค่เพิ่มรายละเอียดที่มีประโยชน์
Neil Miller

11
ฉันได้อัปเดตคำตอบของ Qbericus เพื่อใช้ apk / res-auto เพื่อบันทึกความสับสน
ซับซ้อน

15

คำตอบข้างต้นครอบคลุมทุกอย่างในรายละเอียดที่ดีนอกเหนือจากสองสิ่ง

ก่อนอื่นถ้าไม่มีสไตล์ก็(Context context, AttributeSet attrs)จะใช้ลายเซ็นเมธอดเพื่อตั้งค่าอินสแตนซ์ ในกรณีนี้ใช้context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)เพื่อรับ TypedArray

ประการที่สองมันไม่ครอบคลุมวิธีการจัดการกับทรัพยากร plaurals (สตริงปริมาณ) สิ่งเหล่านี้ไม่สามารถจัดการได้ด้วยการใช้ TypedArray นี่คือข้อมูลโค้ดจาก SeekBarPreference ของฉันที่ตั้งค่าสรุปของการจัดรูปแบบการกำหนดค่าตามค่าของการกำหนดลักษณะ หาก xml สำหรับการกำหนดค่าตามความชอบตั้งค่า android: สรุปเป็นสตริงข้อความหรือสตริงดำเนินการต่อค่าของการกำหนดค่าตามความชอบจะถูกจัดรูปแบบเป็นสตริง (ควรมี% d อยู่เพื่อรับค่า) หาก android: summary ถูกตั้งค่าเป็นรีซอร์ส plaurals แสดงว่าใช้เพื่อจัดรูปแบบผลลัพธ์

// Use your own name space if not using an android resource.
final static private String ANDROID_NS = 
    "http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;

public SeekBarPreference(Context context, AttributeSet attrs) {
    // ...
    TypedArray attributes = context.obtainStyledAttributes(
        attrs, R.styleable.SeekBarPreference);
    pluralResource =  attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
    if (pluralResource !=  0) {
        if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
            pluralResource = 0;
        }
    }
    if (pluralResource ==  0) {
        summary = attributes.getString(
            R.styleable.SeekBarPreference_android_summary);
    }
    attributes.recycle();
}

@Override
public CharSequence getSummary() {
    int value = getPersistedInt(defaultValue);
    if (pluralResource != 0) {
        return resources.getQuantityString(pluralResource, value, value);
    }
    return (summary == null) ? null : String.format(summary, value);
}

  • นี่เป็นเพียงตัวอย่างเท่านั้นหากคุณต้องการล่อลวงให้ตั้งค่าสรุปบนหน้าจอการตั้งค่าคุณจะต้องโทรnotifyChanged()ในonDialogClosedวิธีการของการตั้งค่า

5

วิธีการดั้งเดิมนั้นเต็มไปด้วยรหัสสำเร็จรูปและการจัดการทรัพยากรที่เงอะงะ นั่นเป็นเหตุผลที่ฉันทำกรอบ Spyglass เพื่อสาธิตวิธีการทำงานต่อไปนี้เป็นตัวอย่างที่แสดงวิธีสร้างมุมมองที่กำหนดเองที่แสดงชื่อสตริง

ขั้นตอนที่ 1: สร้างคลาสมุมมองที่กำหนดเอง

public class CustomView extends FrameLayout {
    private TextView titleView;

    public CustomView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @RequiresApi(21)
    public CustomView(
            Context context, 
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {

        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        inflate(getContext(), R.layout.custom_view, this);

        titleView = findViewById(R.id.title_view);
    }
}

ขั้นตอนที่ 2: กำหนดแอตทริบิวต์ของสตริงในvalues/attrs.xmlไฟล์ทรัพยากร:

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

ขั้นตอนที่ 3: ใช้@StringHandlerคำอธิบายประกอบกับsetTitleวิธีการบอกเฟรมเวิร์ก Spyglass เพื่อกำหนดเส้นทางค่าแอททริบิวไปที่เมธอดนี้เมื่อมุมมองขยายเกินจริง

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

ตอนนี้คลาสของคุณมีหมายเหตุประกอบ Spyglass แล้วเฟรมเวิร์ก Spyglass จะตรวจจับได้ในเวลาคอมไพล์และสร้างCustomView_SpyglassCompanionคลาสโดยอัตโนมัติ

ขั้นตอนที่ 4: ใช้คลาสที่สร้างขึ้นในinitวิธีการของมุมมองที่กำหนดเอง:

private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    inflate(getContext(), R.layout.custom_view, this);

    titleView = findViewById(R.id.title_view);

    CustomView_SpyglassCompanion
            .builder()
            .withTarget(this)
            .withContext(getContext())
            .withAttributeSet(attrs)
            .withDefaultStyleAttribute(defStyleAttr)
            .withDefaultStyleResource(defStyleRes)
            .build()
            .callTargetMethodsNow();
}

แค่นั้นแหละ. ตอนนี้เมื่อคุณยกตัวอย่างคลาสจาก XML สหาย Spyglass จะตีความคุณลักษณะและทำการเรียกเมธอดที่ต้องการ ตัวอย่างเช่นถ้าเราขยายเค้าโครงต่อไปนี้setTitleจะถูกเรียกด้วย"Hello, World!"อาร์กิวเมนต์

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="match_parent"
    android:height="match_parent">

    <com.example.CustomView
        android:width="match_parent"
        android:height="match_parent"
        app:title="Hello, World!"/>
</FrameLayout>

เฟรมเวิร์กไม่ได้ จำกัด อยู่ที่รีซอร์สสตริงมีหมายเหตุประกอบต่างกันมากมายสำหรับการจัดการประเภททรัพยากรอื่น ๆ นอกจากนี้ยังมีหมายเหตุประกอบเพื่อกำหนดค่าเริ่มต้นและส่งต่อค่าตัวยึดตำแหน่งหากวิธีการของคุณมีหลายพารามิเตอร์

ดู repo Github สำหรับข้อมูลและตัวอย่างเพิ่มเติม


คุณสามารถทำสิ่งเดียวกันกับการผูกข้อมูลของ Google - หากไม่มีการผูกแอตทริบิวต์สำหรับแอตทริบิวต์ที่เฉพาะเจาะจง GDB จะพยายามหาวิธี set * และใช้แทน android:title="@{&quot;Hello, world!&quot;}"ในกรณีนี้คุณจะต้องเขียนการพูด
Spook

0

หากคุณไม่ใช้แอformatททริบิวต์จากattrองค์ประกอบคุณสามารถใช้มันเพื่ออ้างอิงคลาสจากโครงร่าง XML

  • เช่นจากattrs.xml
  • Android Studio เข้าใจว่าคลาสนั้นถูกอ้างอิงจาก XML
    • กล่าวคือ
      • Refactor > Rename โรงงาน
      • Find Usages โรงงาน
      • และอื่น ๆ ...

อย่าระบุformatแอตทริบิวต์ใน... / src / main / res / values ​​/ attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        ....
        <attr name="give_me_a_class"/>
        ....
    </declare-styleable>

</resources>

ใช้ในไฟล์เลย์เอาต์บางไฟล์... / src / main / res / layout / activity__main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- make sure to use $ dollar signs for nested classes -->
    <MyCustomView
        app:give_me_a_class="class.type.name.Outer$Nested/>

    <MyCustomView
        app:give_me_a_class="class.type.name.AnotherClass/>

</SomeLayout>

แยกชั้นในรหัสการเริ่มต้นมุมมองของคุณ... / src / main / java /.../ MyCustomView.kt

class MyCustomView(
        context:Context,
        attrs:AttributeSet)
    :View(context,attrs)
{
    // parse XML attributes
    ....
    private val giveMeAClass:SomeCustomInterface
    init
    {
        context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
        {
            try
            {
                // very important to use the class loader from the passed-in context
                giveMeAClass = context::class.java.classLoader!!
                        .loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
                        .newInstance() // instantiate using 0-args constructor
                        .let {it as SomeCustomInterface}
            }
            finally
            {
                recycle()
            }
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.