Android - Schreiben einer benutzerdefinierten (zusammengesetzten) Komponente


132

Die Android-App, die ich gerade entwickle, hat eine Hauptaktivität, die ziemlich groß geworden ist. Dies liegt hauptsächlich daran, dass es eine TabWidgetmit 3 Registerkarten enthält. Jede Registerkarte enthält einige Komponenten. Die Aktivität muss alle diese Komponenten gleichzeitig steuern. Ich denke, Sie können sich vorstellen, dass diese Aktivität etwa 20 Felder enthält (ein Feld für fast jede Komponente). Außerdem enthält es viel Logik (klicken Sie auf Listener, Logik zum Füllen von Listen usw.).

Was ich normalerweise in komponentenbasierten Frameworks mache, ist, alles in benutzerdefinierte Komponenten aufzuteilen. Jede benutzerdefinierte Komponente hätte dann eine klare Verantwortung. Es würde einen eigenen Satz von Komponenten und alle anderen mit dieser Komponente verbundenen Logik enthalten.

Ich habe versucht herauszufinden, wie dies gemacht werden kann, und ich habe in der Android-Dokumentation etwas gefunden, was sie gerne als "Compound Control" bezeichnen. (Siehe http://developer.android.com/guide/topics/ui/custom-components.html#compound und scrollen Sie zum Abschnitt "Compound Controls".) Ich möchte eine solche Komponente basierend auf einer XML-Datei erstellen, die das definiert Struktur anzeigen.

In der Dokumentation heißt es:

Beachten Sie, dass Sie wie bei einer Aktivität entweder den deklarativen (XML-basierten) Ansatz zum Erstellen der enthaltenen Komponenten verwenden oder sie programmgesteuert aus Ihrem Code verschachteln können.

Das sind gute Neuigkeiten! Der XML-basierte Ansatz ist genau das, was ich will! Aber es sagt nicht, wie es geht, außer dass es "wie bei einer Aktivität" ist ... Aber was ich in einer Aktivität mache, ist der Aufruf setContentView(...), die Ansichten aus XML aufzublasen. Diese Methode ist nicht verfügbar, wenn Sie beispielsweise eine Unterklasse verwenden LinearLayout.

Also habe ich versucht, das XML manuell wie folgt aufzublasen:

public class MyCompoundComponent extends LinearLayout {

    public MyCompoundComponent(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.my_layout, this);
    }
}

Dies funktioniert mit Ausnahme der Tatsache, dass das von mir geladene XML als Stammelement LinearLayoutdeklariert wurde. Dies führt dazu, dass das aufgeblasene LinearLayoutKind ist, von MyCompoundComponentdem selbst schon ein LinearLayout!! Jetzt haben wir ein redundantes LinearLayout dazwischen MyCompoundComponentund die Ansichten, die es tatsächlich benötigt.

Kann mir bitte jemand einen besseren Weg bieten, um dies zu erreichen und zu vermeiden, dass eine redundante LinearLayoutInstanziierung durchgeführt wird?


14
Ich liebe Fragen, aus denen ich etwas lerne. Vielen Dank.
Jeremy Logan

5
Ich habe kürzlich einen Blogeintrag darüber geschrieben: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Antworten:


101

Verwenden Sie das Merge- Tag als XML-Stammverzeichnis

<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Your Layout -->
</merge>

Überprüfen Sie diesen Artikel.


10
Vielen Dank! Genau das habe ich gesucht. Erstaunlich, wie eine so lange Frage eine so kurze Antwort haben kann. Ausgezeichnet!
Tom van Zummeren

Was ist mit diesem Zusammenführungslayout im Querformat?
Kostadin

2
@Timmmm hahahaha Ich habe diese Frage gestellt, lange bevor es den visuellen Editor überhaupt gab :)
Tom van Zummeren

0

Ich denke, Sie sollten Ihren Klassennamen als XML-Stammelement verwenden:

<com.example.views.MyView xmlns:....
       android:orientation="vertical" etc.>
    <TextView android:id="@+id/text" ... />
</com.example.views.MyView>

Lassen Sie dann Ihre Klasse von dem Layout ableiten, das Sie verwenden möchten. Beachten Sie, dass Sie bei Verwendung dieser Methode den Layout-Inflater hier nicht verwenden.

public class MyView extends LinearLayout
{
    public ConversationListItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public ConversationListItem(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }


    public void setData(String text)
    {
        mTextView.setText(text);
    }

    private TextView mTextView;

    @Override
    protected void onFinishInflate()
    {
        super.onFinishInflate();

        mTextView = (TextView)findViewById(R.id.text);
    }
}

Und dann können Sie Ihre Ansicht wie gewohnt in XML-Layouts verwenden. Wenn Sie die Ansicht programmgesteuert erstellen möchten, müssen Sie sie selbst aufblasen:

MyView v = (MyView)inflater.inflate(R.layout.my_view, parent, false);

Leider können Sie dies nicht tun, v = new MyView(context)da es anscheinend keinen Weg gibt, das Problem mit verschachtelten Layouts zu umgehen. Dies ist also keine vollständige Lösung. Sie können eine Methode wie diese hinzufügen MyView, um sie ein bisschen schöner zu machen:

public static MyView create(Context context)
{
    return (MyView)LayoutInflater.fromContext(context).inflate(R.layout.my_view, null, false);
}

Haftungsausschluss: Ich spreche möglicherweise von kompletten Blöcken.


Vielen Dank! Ich denke, diese Antwort ist auch richtig :) Aber vor drei Jahren, als ich diese Frage stellte, hat "Merge" auch den Trick gemacht!
Tom van Zummeren

8
Und dann kommt jemand und versucht, Ihre benutzerdefinierte Ansicht in einem Layout irgendwo mit just <com.example.views.MyView />und your setDataund onFinishInflatecalls zu verwenden, um NPEs zu werfen, und Sie haben keine Ahnung warum.
Christopher Perry

Der Trick hier besteht darin, Ihre benutzerdefinierte Ansicht zu verwenden und dann im Konstruktor ein Layout aufzublasen, das ein Zusammenführungs-Tag als Stamm verwendet. Jetzt können Sie es in XML verwenden oder einfach neu erstellen. Alle Grundlagen sind abgedeckt, genau das tun die Frage / akzeptierte Antwort zusammen. Was Sie jedoch nicht mehr tun können, ist sich direkt auf das Layout zu beziehen. Es gehört jetzt dem benutzerdefinierten Steuerelement und sollte nur von diesem im Konstruktor verwendet werden. Aber wenn Sie diesen Ansatz verwenden, warum sollten Sie ihn dann irgendwo anders verwenden müssen? Das würdest du nicht.
Mark A. Donohoe
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.