526 Stimmen

Benutzerdefinierte Attribute definieren

Ich muss meine eigenen Attribute implementieren wie in com.android.R.attr

Ich habe nichts in der offiziellen Dokumentation gefunden und benötige daher Informationen darüber, wie diese Attribute zu definieren sind und wie ich sie in meinem Code verwenden kann.

1067voto

Rich Schuler Punkte 41316

Die beste Dokumentation ist derzeit die Quelle. Sie können einen Blick darauf werfen hier (attrs.xml) .

Sie können Attribute im oberen Bereich definieren <resources> Element oder innerhalb eines <declare-styleable> Element. Wenn ich ein Attribut an mehr als einer Stelle verwende, füge ich es in das Root-Element ein. Beachten Sie, dass alle Attribute denselben globalen Namespace nutzen. Das bedeutet, dass selbst wenn Sie ein neues Attribut innerhalb eines <declare-styleable> Element kann es außerhalb des Elements verwendet werden, und Sie können kein weiteres Attribut mit demselben Namen und einem anderen Typ erstellen.

Eine <attr> Element hat zwei xml-Attribute name y format . name können Sie etwas benennen, und so wird im Code darauf verwiesen, z. B., R.attr.my_attribute . Die format kann je nach Art des gewünschten Attributs unterschiedliche Werte haben.

  • Referenz - wenn sie auf eine andere Ressourcen-ID verweist (z. B. "@color/my_color", "@layout/my_layout")
  • Farbe
  • boolean
  • Dimension
  • Schwimmer
  • Ganzzahl
  • String
  • Fraktion
  • enum - normalerweise implizit definiert
  • flag - normalerweise implizit definiert

Sie können das Format auf mehrere Typen einstellen, indem Sie | , z.B., format="reference|color" .

enum Attribute können wie folgt definiert werden:

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

flag Attribute sind ähnlich, außer dass die Werte definiert werden müssen, damit sie miteinander verbunden werden können:

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

Zusätzlich zu den Attributen gibt es die <declare-styleable> Element. Damit können Sie Attribute definieren, die eine benutzerdefinierte Ansicht verwenden kann. Sie tun dies durch die Angabe eines <attr> Element, wenn es zuvor definiert wurde, geben Sie nicht das format . Wenn Sie ein Android-Attribut wiederverwenden möchten, z. B. Android:gravity, dann können Sie dies in der Datei name wie folgt.

Ein Beispiel für eine benutzerdefinierte Ansicht <declare-styleable> :

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

Wenn Sie Ihre benutzerdefinierten Attribute in XML in Ihrer benutzerdefinierten Ansicht definieren, müssen Sie einige Dinge tun. Zunächst müssen Sie einen Namespace deklarieren, um Ihre Attribute zu finden. Dies geschieht im Root-Layoutelement. Normalerweise gibt es nur xmlns:android="http://schemas.android.com/apk/res/android" . Sie müssen nun auch Folgendes hinzufügen 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>

Um schließlich auf das benutzerdefinierte Attribut zuzugreifen, gehen Sie normalerweise im Konstruktor Ihrer benutzerdefinierten Ansicht wie folgt vor.

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

Das Ende. :)

94voto

Neil Miller Punkte 1054

Die Antwort von Qberticus ist gut, aber es fehlt ein nützliches Detail. Wenn Sie diese in einer Bibliothek implementieren ersetzen:

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

mit:

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

Andernfalls wird die Anwendung, die die Bibliothek verwendet, Laufzeitfehler aufweisen.

17voto

Steve Waring Punkte 2756

Die obige Antwort deckt alles sehr detailliert ab, abgesehen von ein paar Dingen.

Erstens, wenn es keine Stile gibt, dann wird die (Context context, AttributeSet attrs) Methodensignatur wird zur Instanziierung der Präferenz verwendet. In diesem Fall verwenden Sie einfach context.obtainStyledAttributes(attrs, R.styleable.MyCustomView) um das TypedArray zu erhalten.

Zweitens wird nicht behandelt, wie man mit plauralen Ressourcen (Mengenstrings) umgeht. Diese können nicht mit TypedArray behandelt werden. Hier ist ein Codeschnipsel aus meiner SeekBarPreference, der die Zusammenfassung der Präferenz setzt und ihren Wert entsprechend dem Wert der Präferenz formatiert. Wenn die xml für die Einstellung Android:summary auf einen Textstring oder eine String-Ressource setzt, wird der Wert der Einstellung in den String formatiert (er sollte %d enthalten, um den Wert aufzufangen). Wenn Android:summary auf eine Plaurals-Ressource gesetzt ist, dann wird diese zur Formatierung des Ergebnisses verwendet.

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

  • Dies ist nur ein Beispiel. Wenn Sie jedoch die Zusammenfassung auf dem Einstellungsbildschirm einstellen möchten, müssen Sie notifyChanged() in der Präferenz der onDialogClosed Methode.

5voto

Helios Punkte 849

Der herkömmliche Ansatz ist voll von unausgegorenem Code und ungeschicktem Umgang mit Ressourcen. Deshalb habe ich die Spyglass Rahmen . Zur Veranschaulichung der Funktionsweise finden Sie hier ein Beispiel für die Erstellung einer benutzerdefinierten Ansicht, die einen String-Titel anzeigt.

Schritt 1: Erstellen Sie eine benutzerdefinierte Ansichtsklasse.

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

Schritt 2: Definieren Sie ein String-Attribut in der values/attrs.xml Ressourcendatei:

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

Schritt 3: Wenden Sie die @StringHandler Anmerkung zu der setTitle Methode, um dem Spyglass-Framework mitzuteilen, dass der Attributwert an diese Methode weitergeleitet werden soll, wenn die Ansicht aufgeblasen wird.

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

Da Ihre Klasse nun eine Spyglass-Annotation hat, wird das Spyglass-Framework sie zur Kompilierzeit erkennen und automatisch die CustomView_SpyglassCompanion Klasse.

Schritt 4: Verwenden Sie die generierte Klasse in der benutzerdefinierten Ansicht der init Methode:

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

Das war's. Wenn Sie nun die Klasse aus XML instanziieren, interpretiert der Spyglass-Begleiter die Attribute und führt den erforderlichen Methodenaufruf durch. Wenn wir zum Beispiel das folgende Layout aufblasen, dann setTitle wird aufgerufen mit "Hello, World!" als Argument.

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

Das Framework ist nicht auf String-Ressourcen beschränkt, sondern verfügt über viele verschiedene Annotationen für andere Ressourcentypen. Es hat auch Annotationen für die Definition von Standardwerten und für die Übergabe von Platzhalterwerten, wenn Ihre Methoden mehrere Parameter haben.

Weitere Informationen und Beispiele finden Sie im Github-Repositorium.

3voto

Eric Punkte 14911

Wenn Sie die format Attribut aus dem attr Element können Sie auf eine Klasse in XML-Layouts verweisen.

  • Beispiel aus attrs.xml .
  • Android Studio versteht, dass die Klasse von XML referenziert wird
    • d.h.
      • Refactor > Rename Werke
      • Find Usages Werke
      • und so weiter...

nicht eine format Attribut in .../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>

in einer Layout-Datei verwenden .../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>

die Klasse im Initialisierungscode Ihrer Ansicht analysieren .../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()
            }
        }
    }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X