Deklarieren eines benutzerdefinierten Android-UI-Elements mithilfe von XML

Antworten:

840

Das Android-Entwicklerhandbuch enthält einen Abschnitt namens Erstellen benutzerdefinierter Komponenten . Leider ist die Diskussion der XML-Attribute nur das Deklarieren des Steuerelements in der Layoutdatei und nicht das tatsächliche Behandeln der Werte in der Klasseninitialisierung. Die Schritte sind wie folgt:

1. Deklarieren Sie Attribute in 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>

Beachten Sie die Verwendung eines nicht qualifizierten Namens im declare-styleableTag. Nicht standardmäßige Android-Attribute wieextraInformation müssen ihren Typ deklarieren lassen. In der Oberklasse deklarierte Tags sind in Unterklassen verfügbar, ohne dass sie erneut deklariert werden müssen.

2. Erstellen Sie Konstruktoren

Da es zwei Konstruktoren gibt, die eine AttributeSetfür die Initialisierung verwenden, ist es zweckmäßig, eine separate Initialisierungsmethode zu erstellen, die die Konstruktoren aufrufen können.

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.MyCustomViewist eine automatisch generierte int[]Ressource, bei der jedes Element die ID eines Attributs ist. Attribute werden für jede Eigenschaft im XML generiert, indem der Attributname an den Elementnamen angehängt wird. Zum Beispiel R.styleable.MyCustomView_android_textenthält das android_textAttribut für MyCustomView. Attribute können dann TypedArraymit verschiedenen getFunktionen aus dem abgerufen werden . Wenn das Attribut nicht im XML definiert ist, nullwird es zurückgegeben. Außer natürlich, wenn der Rückgabetyp ein Grundelement ist. In diesem Fall wird das zweite Argument zurückgegeben.

Wenn Sie nicht alle Attribute abrufen möchten, können Sie dieses Array manuell erstellen. Die ID für Standard-Android-Attribute ist enthalten android.R.attr, während Attribute für dieses Projekt enthalten sind R.attr.

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

Bitte beachten Sie, dass Sie sollten nicht alles in Verwendung android.R.styleable, gemäß diesem Thread es in Zukunft ändern kann. Es ist immer noch in der Dokumentation enthalten, dass es nützlich ist, alle diese Konstanten an einem Ort anzuzeigen.

3. Verwenden Sie es in Layoutdateien wie layout\main.xml

Fügen Sie die Namespace-Deklaration xmlns:app="http://schemas.android.com/apk/res-auto"in das XML-Element der obersten Ebene ein. Namespaces bieten eine Methode, um Konflikte zu vermeiden, die manchmal auftreten, wenn verschiedene Schemas dieselben Elementnamen verwenden ( weitere Informationen finden Sie in diesem Artikel ). Die URL ist einfach eine Möglichkeit , Schemas eindeutig zu identifizieren - unter dieser URL muss eigentlich nichts gehostet werden . Wenn dies anscheinend nichts bewirkt, müssen Sie das Namespace-Präfix nur hinzufügen, wenn Sie einen Konflikt lösen müssen.

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

Verweisen Sie auf die benutzerdefinierte Ansicht mit dem vollständig qualifizierten Namen.

Android LabelView-Beispiel

Wenn Sie ein vollständiges Beispiel wünschen, schauen Sie sich das Beispiel für die Android-Etikettenansicht an.

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

Dies ist in a LinearLayoutmit einem Namespace-Attribut enthalten:xmlns:app="http://schemas.android.com/apk/res-auto"

Links

Casebash
quelle
14
Ich möchte hinzufügen, dass Sie, wenn Ihr Root-Element Ihren benutzerdefinierten Namespace benötigt, sowohl den Standard-Android-Namespace als auch Ihren eigenen benutzerdefinierten hinzufügen müssen, da sonst möglicherweise Build-Fehler auftreten.
Chase
11
Diese Antwort ist die klarste Ressource im Internet für benutzerdefinierte XML-Parameter, die ich finden konnte. Vielen Dank, Casebash.
Artem Russakovskii
2
Aus irgendeinem Grund weigert sich der visuelle Editor, den geschriebenen Textwert für android: text zu verwenden, aber das Gerät verwendet ihn einwandfrei. Woher ?
Android-Entwickler
2
@androiddeveloper Es scheint, dass der Eclipse-Editor die Verwendung der Werte für alle android: -Attribute ablehnt. Ich würde gerne wissen, ob es sich um ein Feature oder einen Bug handelt
deej
4
Was ist der Zweck von xmlns: App-Namespace und Res-Auto?
IgorGanapolsky
91

Tolle Referenz. Vielen Dank! Eine Ergänzung dazu:

Wenn zufällig ein Bibliotheksprojekt enthalten ist, das benutzerdefinierte Attribute für eine benutzerdefinierte Ansicht deklariert hat, müssen Sie Ihren Projektnamespace deklarieren, nicht den der Bibliothek. Z.B:

Vorausgesetzt, die Bibliothek hat das Paket "com.example.library.customview" und das Arbeitsprojekt hat das Paket "com.example.customview", dann:

Funktioniert nicht (zeigt den Fehler "Fehler: Keine Ressourcen-ID für Attribut 'newAttr' im Paket 'com.example.library.customview' gefunden"):

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

Wird funktionieren:

<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" />
Andy
quelle
47
Dies wurde in der ADT 17-Vorschau behoben. So verwenden Sie den Namespace der App aus der Bibliothek: xmlns:app="http://schemas.android.com/apk/res-auto"Siehe Kommentar 57 in code.google.com/p/android/issues/detail?id=9656
nmr
2
Das Einschließen Ihres benutzerdefinierten Namespace gibt jetzt einen Fehler zurückSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson
Der benutzerdefinierte Namespace endet mit res-auto, da wir Android Studio und Gradle verwenden. Andernfalls (z. B. einige Eclipse-Versionen) endet es normalerweise in lib / [Ihr Paketname]
Universe
Der benutzerdefinierte Namespace endet damit, res-autodass wir Android Studio und Gradle verwenden. Andernfalls (z. B. einige Eclipse-Versionen) würde es normalerweise enden lib/[your package name]. dhhttp://schemas.android.com/apk/lib/[your package name]
Universum
27

Ergänzung zu den am häufigsten gewählten Antworten.

getStyledAttributes ()

Ich möchte einige Wörter zur Verwendung von getStyledAttributes () hinzufügen, wenn wir eine benutzerdefinierte Ansicht mit den definierten Attributen android: xxx erstellen. Besonders wenn wir TextAppearance verwenden.
Wie in "2. Erstellen von Konstruktoren" erwähnt, erhält die benutzerdefinierte Ansicht bei ihrer Erstellung AttributeSet. Die Hauptverwendung finden Sie im TextView-Quellcode (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();

Was können wir hier sehen?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
Der Attributsatz wird gemäß der Dokumentation thematisch verarbeitet. Attributwerte werden Schritt für Schritt zusammengestellt. Zuerst werden Attribute aus dem Thema gefüllt, dann werden Werte durch Werte aus dem Stil ersetzt, und schließlich ersetzen exakte Werte aus XML für eine spezielle Ansichtsinstanz andere.
Array angeforderter Attribute - com.android.internal.R.styleable.TextView
Dies ist ein gewöhnliches Array von Konstanten. Wenn wir Standardattribute anfordern, können wir dieses Array manuell erstellen.

Was in der Dokumentation nicht erwähnt wird - Reihenfolge der Ergebnisse TypedArray-Elemente.
Wenn die benutzerdefinierte Ansicht in attrs.xml deklariert wird, werden spezielle Konstanten für Attributindizes generiert. Und wir können Werte folgendermaßen extrahieren : a.getString(R.styleable.MyCustomView_android_text). Aber für das Handbuchint[] gibt es jedoch keine Konstanten. Ich nehme an, dass getXXXValue (arrayIndex) gut funktioniert.

Eine andere Frage lautet: "Wie können wir interne Konstanten ersetzen und Standardattribute anfordern?" Wir können android.R.attr. * Werte verwenden.

Wenn wir also das Standardattribut TextAppearance in der benutzerdefinierten Ansicht verwenden und seine Werte im Konstruktor lesen möchten, können wir den Code aus TextView folgendermaßen ändern:

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

Wo CustomLabel definiert ist:

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

Vielleicht irre ich mich irgendwie, aber die Android-Dokumentation zu getStyledAttributes () ist sehr schlecht.

Standard-UI-Komponente erweitern

Gleichzeitig können wir einfach die Standard-UI-Komponente unter Verwendung aller deklarierten Attribute erweitern. Dieser Ansatz ist nicht so gut, weil beispielsweise TextView viele Eigenschaften deklariert. Und es wird unmöglich sein, die volle Funktionalität beim Überschreiben von onMeasure () und onDraw () zu implementieren.

Wir können jedoch die theoretisch weite Wiederverwendung von benutzerdefinierten Komponenten opfern. Sagen Sie "Ich weiß genau, welche Funktionen ich verwenden werde" und geben Sie keinen Code an Dritte weiter.

Dann können wir den Konstruktor implementieren CustomComponent(Context, AttributeSet, defStyle). Nach dem Aufruf super(...)werden alle Attribute analysiert und über Getter-Methoden verfügbar sein.

yuriy.weiss
quelle
Funktionieren vordefinierte Attribute für Android: xxx in Eclipse GUI Designer?
DJ
Solche Attribute werden vom Eclipse ADT-Plugin im Eigenschafteneditor erkannt. Ich kann Standardeinstellungen aus meinem Stil sehen, wenn einige Werte nicht definiert sind. Und vergessen Sie nicht, Ihrer Klasse eine @ RemoteView-Anmerkung hinzuzufügen.
yuriy.weiss
Kann es nicht zum Laufen bringen. Eclipse lädt weiterhin Nullen für getText und löst android.content.res.Resources $ NotFoundException für getResourceId aus, obwohl die App auf einem Gerät gut ausgeführt wird.
DJ
Entschuldigung, ich kann dir nicht helfen. Ich habe nur ein Demo-Projekt erstellt, um Möglichkeiten zu testen, und bin solchen Fehlern nicht begegnet.
yuriy.weiss
Dies ist viel besser, als benutzerdefinierte Attribute einer benutzerdefinierten Ansicht den integrierten Attributen der darin enthaltenen integrierten Ansichten zuzuordnen.
Samis
13

Es scheint, dass Google seine Entwicklerseite aktualisiert und dort verschiedene Schulungen hinzugefügt hat.

Einer von ihnen beschäftigt sich mit der Erstellung von benutzerdefinierten Ansichten und können gefunden werden hier

mitch000001
quelle
5

Vielen Dank für die erste Antwort.

Ich hatte nur ein Problem damit. Beim Aufblasen meiner Ansicht hatte ich einen Fehler: java.lang.NoSuchMethodException: MyView (Kontext, Attribute)

Ich habe es gelöst, indem ich einen neuen Konstruktor erstellt habe:

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

Hoffe das wird helfen!

user2346922
quelle
0

Sie können jede Layoutdatei in eine andere Layoutdatei aufnehmen.

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

Hier sind die Layoutdateien im Include-Tag andere XML-Layoutdateien im selben res-Ordner.

Akshay Paliwal
quelle
Ich habe dies versucht, das Problem, das ich damit habe, ist, dass das enthaltene Layout nicht "angepasst" werden kann, keine Generika erstellen kann. Wenn ich zum Beispiel eine Schaltfläche auf ähnliche Weise einbinde und versuche, den Text in der XML-Datei festzulegen, funktioniert dies.
cfl