Menentukan attr khusus

472

Saya perlu menerapkan atribut saya sendiri seperti di com.android.R.attr

Tidak menemukan apa pun dalam dokumentasi resmi sehingga saya memerlukan informasi tentang cara mendefinisikan attrs ini dan cara menggunakannya dari kode saya.

Alexander Oleynikov
sumber
20
Dokumen-dokumen ini mungkin lebih baru dari yang Anda posting, tetapi untuk menjaga agar tetap terkini, Anda dapat menemukan dokumentasi resmi yang bagus untuk atribut di sini: developer.android.com/training/custom-views/…
OYRM
Saya merekomendasikan artikel yang bagus dengan contoh tentang atribut khusus: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński
contoh kerja kecil mungkin bermanfaat: github.com/yujiaao/MergeLayout1
Yu Jiaao

Jawaban:

971

Saat ini dokumentasi terbaik adalah sumbernya. Anda dapat melihatnya di sini (attrs.xml) .

Anda bisa mendefinisikan atribut di <resources>elemen atas atau di dalam <declare-styleable>elemen. Jika saya akan menggunakan attr di lebih dari satu tempat saya letakkan di elemen root. Catatan, semua atribut memiliki namespace global yang sama. Itu berarti bahwa bahkan jika Anda membuat atribut baru di dalam <declare-styleable>elemen itu dapat digunakan di luarnya dan Anda tidak dapat membuat atribut lain dengan nama yang sama dari tipe yang berbeda.

Suatu <attr>elemen memiliki dua atribut xml namedan format. namememungkinkan Anda menyebutnya sesuatu dan ini adalah bagaimana Anda akhirnya menyebutnya dalam kode, misalnya R.attr.my_attribute,. The formatatribut dapat memiliki nilai yang berbeda tergantung pada 'jenis' atribut yang Anda inginkan.

  • referensi - jika referensi id sumber daya lain (misalnya, "@ color / my_color", "@ layout / my_layout")
  • warna
  • boolean
  • dimensi
  • mengapung
  • bilangan bulat
  • tali
  • pecahan
  • enum - biasanya didefinisikan secara implisit
  • flag - biasanya didefinisikan secara implisit

Anda dapat mengatur format ke beberapa jenis dengan menggunakan |, misalnya format="reference|color",.

enum atribut dapat didefinisikan sebagai berikut:

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

flag atribut serupa kecuali nilai-nilai perlu didefinisikan sehingga mereka dapat digigit bersama:

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

Selain atribut ada <declare-styleable>elemen. Ini memungkinkan Anda untuk mendefinisikan atribut yang bisa digunakan tampilan kustom. Anda melakukan ini dengan menentukan <attr>elemen, jika sebelumnya ditentukan Anda tidak menentukan format. Jika Anda ingin menggunakan kembali Android attr, misalnya, Android: Gravity, maka Anda dapat melakukannya diname , sebagai berikut.

Contoh tampilan khusus <declare-styleable>:

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

Ketika mendefinisikan atribut khusus Anda dalam XML pada tampilan kustom Anda, Anda perlu melakukan beberapa hal. Pertama, Anda harus mendeklarasikan namespace untuk menemukan atribut Anda. Anda melakukan ini pada elemen tata letak root. Biasanya hanya ada xmlns:android="http://schemas.android.com/apk/res/android". Anda sekarang juga harus menambahkanxmlns:whatever="http://schemas.android.com/apk/res-auto" .

Contoh:

<?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>

Terakhir, untuk mengakses atribut khusus yang biasanya Anda lakukan di konstruktor tampilan kustom Anda sebagai berikut.

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();
}

Tamat. :)

Schuler yang kaya
sumber
14
Berikut ini adalah contoh proyek yang menunjukkan atribut khusus untuk digunakan dengan kustom View: github.com/commonsguy/cw-advandroid/tree/master/Views/…
CommonsWare
7
Jika Anda menggunakan attr kustom dari proyek perpustakaan: lihat pertanyaan ini: stackoverflow.com/questions/5819369/… - Tampaknya berfungsi jika Anda menggunakan xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- tanpa menyalin attrs.xml. Perhatikan bahwa jalur URI namespace harus / apk / * lib * not / apk / res.
thom_nic
2
@ThomNichols apk/libtriknya tidak berfungsi untuk saya pada atribut khusus dengan format referensi dari proyek perpustakaan. Apa yang melakukan pekerjaan adalah untuk digunakan apk/res-auto, seperti yang disarankan dalam stackoverflow.com/a/13420366/22904 tepat di bawah dan juga di stackoverflow.com/a/10217752
Giulio Piancastelli
1
Mengutip @Qberticus: "atribut flag serupa kecuali nilai-nilai perlu didefinisikan sehingga mereka dapat digigit bersama-sama". Menurut pendapat saya ini adalah semacam mengecilkan perbedaan utama antara enumdan flag: yang pertama memungkinkan kita memilih satu dan hanya satu nilai, yang terakhir memungkinkan kita menggabungkan beberapa. Saya menulis jawaban yang lebih panjang dalam pertanyaan yang sama di sini , dan setelah sekarang menemukan pertanyaan ini saya pikir saya akan terhubung ke itu
Rad Haring
5
a.recycle()sangat penting di sini untuk membebaskan memori
Tash Pemhiwa
87

Jawaban Qberticus baik, tetapi satu detail yang berguna tidak ada. Jika Anda menerapkan ini di perpustakaan ganti:

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

dengan:

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

Kalau tidak, aplikasi yang menggunakan perpustakaan akan memiliki kesalahan runtime.

Neil Miller
sumber
3
Ini baru saja ditambahkan ... Saya pikir dalam beberapa minggu yang lalu. Tentu saja itu ditambahkan lama setelah Qberticus menulis jawabannya.
ArtOfWarfare
12
Saya pikir ini lebih tua dari itu, tetapi tentu saja ditambahkan lama setelah Qberticus menulis jawabannya. Sama sekali tidak menyalahkannya, hanya menambahkan detail yang bermanfaat.
Neil Miller
11
Saya telah memperbarui jawaban Qbericus untuk menggunakan apk / res-auto untuk menyimpan kebingungan.
Intrications
15

Jawaban di atas mencakup semuanya dengan sangat terperinci, terlepas dari beberapa hal.

Pertama, jika tidak ada gaya, maka (Context context, AttributeSet attrs)tanda tangan metode akan digunakan untuk instantiate preferensi. Dalam hal ini gunakan sajacontext.obtainStyledAttributes(attrs, R.styleable.MyCustomView) untuk mendapatkan TypedArray.

Kedua tidak mencakup cara menangani sumber daya plaur (string kuantitas). Ini tidak bisa ditangani dengan menggunakan TypedArray. Berikut ini cuplikan kode dari SeekBarPreference saya yang menetapkan ringkasan preferensi yang memformat nilainya sesuai dengan nilai preferensi. Jika xml untuk preferensi menyetel android: ringkasan ke string teks atau string, resouce nilai preferensi diformat ke dalam string (seharusnya memiliki% d di dalamnya, untuk mengambil nilai). Jika android: ringkasan diatur ke sumber daya plaur, maka itu digunakan untuk memformat hasilnya.

// 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);
}

  • Ini hanya diberikan sebagai contoh, namun, jika Anda ingin tergoda untuk mengatur ringkasan pada layar preferensi, maka Anda perlu memanggil notifyChanged()metode preferensi onDialogClosed.
Steve Waring
sumber
5

Pendekatan tradisional penuh dengan kode boilerplate dan penanganan sumber daya yang canggung. Itu sebabnya saya membuat kerangka kerja Spyglass . Untuk mendemonstrasikan cara kerjanya, berikut adalah contoh yang menunjukkan cara membuat tampilan kustom yang menampilkan judul String.

Langkah 1: Buat kelas tampilan kustom.

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

Langkah 2: Tentukan atribut string dalam values/attrs.xmlfile sumber daya:

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

Langkah 3: Terapkan @StringHandleranotasi ke setTitlemetode untuk memberi tahu kerangka kerja Spyglass untuk merutekan nilai atribut ke metode ini saat tampilan meningkat.

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

Sekarang kelas Anda memiliki anotasi Spyglass, kerangka kerja Spyglass akan mendeteksinya pada waktu kompilasi dan secara otomatis menghasilkan CustomView_SpyglassCompanion kelas.

Langkah 4: Gunakan kelas yang dihasilkan dalam metode tampilan kustom 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();
}

Itu dia. Sekarang ketika Anda instantiate kelas dari XML, pendamping Spyglass menafsirkan atribut dan membuat panggilan metode yang diperlukan. Misalnya, jika kita mengembang tata letak berikut maka setTitleakan dipanggil dengan "Hello, World!"sebagai argumen.

<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>

Kerangka kerja tidak terbatas pada sumber daya string memiliki banyak anotasi yang berbeda untuk menangani jenis sumber daya lainnya. Ini juga memiliki anotasi untuk menentukan nilai default dan untuk meneruskan nilai placeholder jika metode Anda memiliki beberapa parameter.

Lihat repo Github untuk informasi dan contoh lebih lanjut.

Helios
sumber
Anda dapat mencapai hal yang sama dengan Google Data Binding - jika tidak ada atribut yang mengikat untuk atribut tertentu, GDB mencoba menemukan metode set * dan menggunakannya. Dalam hal ini Anda harus menulis, katakanlah android:title="@{&quot;Hello, world!&quot;}".
Spook
0

jika Anda menghilangkan formatatribut dari attrelemen, Anda bisa menggunakannya untuk referensi kelas dari tata letak XML.

  • contoh dari attrs.xml .
  • Android Studio memahami bahwa kelas sedang direferensikan dari XML
    • yaitu
      • Refactor > Rename bekerja
      • Find Usages bekerja
      • dan seterusnya...

jangan tentukan formatatribut di ... / 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>

gunakan di beberapa file layout ... / 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>

parsing kelas dalam kode inisialisasi tampilan Anda ... / 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()
            }
        }
    }
Eric
sumber