Definizione di attr personalizzati


472

Devo implementare i miei attributi come in com.android.R.attr

Non ho trovato nulla nella documentazione ufficiale, quindi ho bisogno di informazioni su come definire questi attr e su come usarli dal mio codice.


20
Questi documenti potrebbero essere più recenti del tuo post, ma per mantenerlo aggiornato, puoi trovare una buona documentazione ufficiale per gli attributi qui: developer.android.com/training/custom-views/…
OYRM

Consiglio un bell'articolo con un esempio degli attributi personalizzati: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński

un piccolo esempio di lavoro può essere utile: github.com/yujiaao/MergeLayout1
Yu Jiaao

Risposte:


971

Attualmente la migliore documentazione è la fonte. Puoi dare un'occhiata qui (attrs.xml) .

È possibile definire gli attributi nell'elemento superiore <resources>o all'interno di un <declare-styleable>elemento. Se userò un attr in più di un posto, lo inserirò nell'elemento root. Nota, tutti gli attributi condividono lo stesso spazio dei nomi globale. Ciò significa che anche se si crea un nuovo attributo all'interno di a<declare-styleable> elemento, può essere utilizzato al di fuori di esso e non è possibile creare un altro attributo con lo stesso nome di un tipo diverso.

Un <attr>elemento ha due attributi xml namee format. nameti permette di chiamarlo qualcosa e questo è il modo in cui finisci per riferirti ad esso nel codice, ad es R.attr.my_attribute. L' formatattributo può avere valori diversi a seconda del 'tipo' di attributo desiderato.

  • riferimento - se fa riferimento a un altro ID risorsa (ad es. "@ color / my_color", "@ layout / my_layout")
  • colore
  • booleano
  • dimensione
  • galleggiante
  • numero intero
  • corda
  • frazione
  • enum - normalmente definito implicitamente
  • flag - normalmente definito implicitamente

È possibile impostare il formato a più tipi utilizzando |, ad esempio, format="reference|color".

enum gli attributi possono essere definiti come segue:

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

flag gli attributi sono simili, ad eccezione dei valori che devono essere definiti in modo che possano essere bit bited insieme:

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

Oltre agli attributi c'è l' <declare-styleable>elemento. Ciò consente di definire gli attributi che una vista personalizzata può utilizzare. Puoi farlo specificando un <attr>elemento, se è stato precedentemente definito non specifichi format. Se desideri riutilizzare un attr android, ad esempio android: gravità, puoi farlo nel name, come segue.

Un esempio di una vista personalizzata <declare-styleable>:

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

Quando si definiscono gli attributi personalizzati in XML nella vista personalizzata, è necessario eseguire alcune operazioni. Per prima cosa devi dichiarare uno spazio dei nomi per trovare i tuoi attributi. Lo fai sull'elemento del layout di root. Normalmente c'è solo xmlns:android="http://schemas.android.com/apk/res/android". Ora devi anche aggiungerexmlns:whatever="http://schemas.android.com/apk/res-auto" .

Esempio:

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

Infine, per accedere a quell'attributo personalizzato, normalmente lo fai nel costruttore della tua vista personalizzata come segue.

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

La fine. :)


14
Ecco un progetto di esempio che dimostra gli attributi personalizzati da utilizzare con un'abitudine View: github.com/commonsguy/cw-advandroid/tree/master/Views/…
CommonsWare

7
Se stai usando attr personalizzati da un progetto di libreria: vedi questa domanda: stackoverflow.com/questions/5819369/… - Sembra funzionare se lo usi xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- nessuna copia attrs.xml. Nota il percorso URI dello spazio dei nomi deve essere / apk / * lib * non / apk / res.
thom_nic

2
@ThomNichols il apk/libtrucco non ha funzionato per me su attributi personalizzati con formato di riferimento da un progetto di libreria. Quello che ha funzionato è stato usare apk/res-auto, come suggerito in stackoverflow.com/a/13420366/22904 appena sotto e anche in stackoverflow.com/a/10217752
Giulio Piancastelli,

1
Citando @Qberticus: "gli attributi del flag sono simili, tranne per il fatto che i valori devono essere definiti in modo che possano essere bit bited insieme". Secondo me, questo è un po 'sottovalutare la differenza principale tra enume flag: il primo ci consente di scegliere un solo valore, il secondo ci consente di combinarne diversi. Ho scritto una risposta più lunga in una domanda simile qui , e dopo aver trovato questa domanda ho pensato di collegarmi a quello.
Rad Haring,

5
a.recycle()è molto importante qui per liberare memoria
Tash Pemhiwa,

87

La risposta di Qberticus è buona, ma manca un dettaglio utile. Se li stai implementando in una libreria sostituisci:

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

con:

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

In caso contrario, l'applicazione che utilizza la libreria avrà errori di runtime.


3
Questo è stato aggiunto solo di recente ... Penso che poche settimane fa. Certamente fu aggiunto molto tempo dopo che Qberticus scrisse la sua risposta.
ArtOfWarfare il

12
Penso che sia più vecchio di quello, ma sicuramente è stato aggiunto molto tempo dopo che Qberticus ha scritto la sua risposta. Non lo critico affatto, basta aggiungere un dettaglio utile.
Neil Miller,

11
Ho aggiornato la risposta di Qbericus per usare apk / res-auto per salvare la confusione.
Intricazioni

15

La risposta sopra copre tutto nei minimi dettagli, a parte un paio di cose.

Innanzitutto, se non ci sono stili, (Context context, AttributeSet attrs)verrà utilizzata la firma del metodo per creare un'istanza della preferenza. In questo caso basta usare context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)per ottenere TypedArray.

In secondo luogo, non tratta il modo in cui gestire le risorse plaurali (stringhe di quantità). Non è possibile gestirli utilizzando TypedArray. Ecco uno snippet di codice dal mio SeekBarPreference che imposta il riepilogo della preferenza formattandone il valore in base al valore della preferenza. Se l'xml per la preferenza imposta android: il riepilogo su una stringa di testo o su una stringa, il valore della preferenza viene formattato nella stringa (dovrebbe contenere% d al suo interno, per raccogliere il valore). Se android: il riepilogo è impostato su una risorsa plaurals, viene utilizzato per formattare il risultato.

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

  • Questo è solo un esempio, tuttavia, se si desidera essere tentati di impostare il riepilogo nella schermata delle preferenze, è necessario chiamare notifyChanged()il onDialogClosedmetodo della preferenza .

5

L'approccio tradizionale è pieno di codice boilerplate e gestione delle risorse maldestra. Ecco perché ho realizzato il framework Spyglass . Per dimostrare come funziona, ecco un esempio che mostra come creare una vista personalizzata che mostri un titolo String.

Passaggio 1: creare una classe di visualizzazione personalizzata.

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

Passaggio 2: definire un attributo stringa nel values/attrs.xmlfile di risorse:

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

Passaggio 3: applicare l' @StringHandlerannotazione al setTitlemetodo per indicare al framework Spyglass di instradare il valore dell'attributo a questo metodo quando la vista è gonfiata.

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

Ora che la tua classe ha un'annotazione Spyglass, il framework Spyglass la rileverà in fase di compilazione e genererà automaticamente la CustomView_SpyglassCompanionclasse.

Passaggio 4: utilizzare la classe generata nel initmetodo della vista personalizzata :

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

Questo è tutto. Ora, quando si crea un'istanza della classe da XML, il compagno Spyglass interpreta gli attributi e fa chiamare il metodo richiesto. Ad esempio, se si gonfia il seguente layout, setTitleverrà chiamato "Hello, World!"come argomento.

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

Il framework non è limitato alle risorse di stringa ha molte annotazioni diverse per la gestione di altri tipi di risorse. Ha anche annotazioni per la definizione di valori predefiniti e per il passaggio di valori segnaposto se i metodi hanno più parametri.

Dai un'occhiata al repository Github per ulteriori informazioni ed esempi.


Puoi ottenere lo stesso risultato con Google Data Binding: se non esiste alcun legame di attributo per attributo specifico, GDB prova a trovare il metodo set * e lo utilizza invece. In questo caso dovresti scrivere, per esempio android:title="@{&quot;Hello, world!&quot;}".
Spook

0

se si omette l' formatattributo attrdall'elemento, è possibile utilizzarlo per fare riferimento a una classe dai layout XML.

  • esempio da attrs.xml .
  • Android Studio comprende che la classe viene referenziata da XML
    • vale a dire
      • Refactor > Rename lavori
      • Find Usages lavori
      • e così via...

non specificare un formatattributo in ... / src / main / res / valori / attrs.xml

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

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

</resources>

usalo in alcuni file di 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>

analizzare la classe nel codice di inizializzazione della vista ... / 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()
            }
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.