Android - pisanie niestandardowego (złożonego) komponentu

132

Główna działalność aplikacji na Androida, którą obecnie tworzę, jest dość duża. Dzieje się tak głównie dlatego, że zawiera TabWidget3 zakładki. Każda zakładka ma sporo elementów. Aktywność musi kontrolować wszystkie te komponenty jednocześnie. Myślę więc, że możesz sobie wyobrazić, że to działanie ma około 20 pól (pole dla prawie każdego komponentu). Zawiera również dużo logiki (słuchacze kliknięć, logika wypełniania list itp.).

To, co zwykle robię w frameworkach opartych na komponentach, to dzielenie wszystkiego na niestandardowe komponenty. Każdy komponent niestandardowy miałby wtedy wyraźną odpowiedzialność. Zawierałby własny zestaw komponentów i całą inną logikę związaną z tym komponentem.

Próbowałem wymyślić, jak można to zrobić, i znalazłem w dokumentacji Androida coś, co nazywają „Kontrolą złożoną”. (Zobacz http://developer.android.com/guide/topics/ui/custom-components.html#compound i przewiń do sekcji „Compound Controls”). Chciałbym utworzyć taki komponent na podstawie pliku XML definiującego zobacz strukturę.

W dokumentacji jest napisane:

Należy zauważyć, że podobnie jak w przypadku działania, można użyć podejścia deklaratywnego (opartego na języku XML) do tworzenia zawartych składników lub zagnieżdżać je programowo na podstawie kodu.

Cóż, to dobra wiadomość! Podejście oparte na XML jest dokładnie tym, czego chcę! Ale nie mówi, jak to zrobić, z wyjątkiem tego, że jest to „jak w przypadku działania” ... Ale to, co robię w działaniu, to wywołanie setContentView(...)zawyżenia widoków z XML. Ta metoda nie jest dostępna, jeśli na przykład masz podklasę LinearLayout.

Więc próbowałem ręcznie zawyżyć XML w ten sposób:

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

To działa, z wyjątkiem faktu, że ładowany plik XML został LinearLayoutzadeklarowany jako element główny. Skutkuje to nadymaniem LinearLayoutbycia dzieckiem, MyCompoundComponentktórego już jest LinearLayout!! Więc teraz mamy zbędny LinearLayout pomiędzy MyCompoundComponenti widoki, których faktycznie potrzebuje.

Czy ktoś może mi zapewnić lepszy sposób rozwiązania tego problemu, unikając LinearLayouttworzenia zbędnej instancji?

Tom van Zummeren
źródło
14
Uwielbiam pytania, z których się czegoś uczę. Dzięki.
Jeremy Logan
5
Niedawno napisałem wpis na blogu o tym: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Odpowiedzi:

101

Użyj tagu scalającego jako katalogu głównego XML

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

Sprawdź ten artykuł.

bhatt4982
źródło
10
Dziękuję Ci bardzo! To jest dokładnie to, czego szukałem. Niesamowite, jak tak długie pytanie może mieć tak krótką odpowiedź. Świetny!
Tom van Zummeren
A co z umieszczeniem tego układu scalania w poziomie?
Kostadin,
2
@Timmmm hahahaha Zadałem to pytanie na długo przed istnieniem edytora wizualnego :)
Tom van Zummeren
0

Myślę, że sposób, w jaki powinieneś to zrobić, polega na użyciu nazwy klasy jako elementu głównego XML:

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

A potem niech twoja klasa zostanie wyprowadzona z dowolnego układu, którego chcesz użyć. Zauważ, że jeśli używasz tej metody, nie używasz tutaj inflatora układu.

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

Następnie możesz normalnie używać swojego widoku w układach XML. Jeśli chcesz zrobić widok programowo, musisz sam go zawyżyć:

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

Niestety nie pozwala ci to zrobić, v = new MyView(context)ponieważ wydaje się, że nie ma sposobu na obejście problemu zagnieżdżonych układów, więc nie jest to tak naprawdę pełne rozwiązanie. Możesz dodać taką metodę, MyViewaby była trochę ładniejsza:

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

Zastrzeżenie: może mówię kompletne bzdury.

Timmmm
źródło
Dzięki! Myślę, że ta odpowiedź też jest poprawna :) Ale trzy lata temu, kiedy zadałem to pytanie, „scalanie” też się udało!
Tom van Zummeren
8
A potem ktoś przychodzi i próbuje użyć twojego niestandardowego widoku w układzie gdzieś z just, <com.example.views.MyView />a twoje setDatai onFinishInflatewywołania zaczynają rzucać NPE, a nie masz pojęcia, dlaczego.
Christopher Perry,
Sztuczka polega na tym, aby użyć własnego widoku, a następnie w konstruktorze rozszerzyć układ, który używa tagu scalającego jako elementu głównego. Teraz możesz go używać w XML lub po prostu wprowadzając go na nowo. Wszystkie podstawy są uwzględnione, co jest dokładnie tym, co razem daje pytanie / zaakceptowana odpowiedź. Jednak nie możesz już bezpośrednio odwoływać się do układu. Jest teraz „własnością” kontrolki niestandardowej i powinien być używany tylko przez nią w konstruktorze. Ale jeśli używasz tego podejścia, po co i tak miałbyś go używać gdziekolwiek indziej? Nie zrobiłbyś tego.
Mark A. Donohoe