2014.02.20 18:23
[programowanie] po co na Androidzie przy nadmuchiwaniu lejałtu można podać rodzica?
Jak się inflaterem nadmuchuje widok z XML-a z zasobów, to oprócz numerka zasobu (na przykład R.layout.mójlayout) podaje się też drugi parametr, którym ma być widok, który w przyszłości będzie rodzicem tego właśnie nadmuchiwanego widoku. Zawsze nie wiedziałem, po co się go podaje, i ja go nie podawałem, dawałem w tym drugim parametrze null i też działało. Wreszcie dowiedziałem się, o co z tym chodzi - a chodzi o to:
Kiedy dodaje się widok do kontenera, trzeba podać obiekt klasy LayoutParams albo potomnej zawierający parametry dla kontenera określające sposób, w jaki kontener ma rozmieścić ten dodawany widok. Waniliowy LayoutParams ma tylko dwa atrybuty: width i height, określają one, jaką wysokość i szerokość ma dostać nasz widok w tym kontenerze. Stworzenie obiektu layoutparams i dostarczenie go kontenerowi może odbyć się na kilka sposobów. Można stworzyć obiekt layoutparams przez "new LayoutParams". Potem można ten obiekt layoutparams przekazać kontenerowi przy wywoływaniu metody ViewGroup#addView, obok dodawanego widoku. Ale można też wsadzić ten layout w widok, metodą View#setLayoutParams. Wtedy ten widok niesie w sobie layoutparams z ustawieniami, jak chce być włożony w kontener, i można metodzie ViewGroup#addView przekazać tylko go (czyli tylko ten widok), a nie przekazywać już obiektu layoutparams. Można też napisać parametry dla layoutu w pliku XML z layoutem, jako iksemelowe atrybuty o nazwach layout_cośtam. Tylko z tym pisaniem parametrów lejałtowych w pliku XML z layoutem wiąże się ciekawy problem. Bo poszczególne kontenery (różne klasy dziedziczące po ViewGroup) tworzą własne klasy dziedziczące po LayoutParams (one zawsze - taki zwyczaj - są statycznymi klasami wewnętrznymi siedzącymi w tych klasach dziedziczących po ViewGroup i nadal nazywają się LayoutParams - więc jest na przykład klasa LinearLayout.LayoutParams) i rozbudowujące je o dodatkowe atrybuty. Na przykład klasie LinearLayout towarzyszy klasa LinearLayout.LayoutParams, w której oprócz zwykłych atrybutów width i height jest jeszcze atrybut gravity mówiący, w którą stronę ma być dosunięty dany element. Więc jak inflater nadmuchuje dany widok z XML-a, a autor tego XML-a umieścił w nim różne atrybuty lejałtowe, to pewnie trzeba by te atrybuty lejałtowe umieścić nie w obiekcie waniliowej klasy LayoutParams, tylko w którejś z klas dziedziczących po LayoutParams. Ale jakiej? Żeby to wiedzieć, trzeba wiedzieć, w jakim kontenerze będzie siedzieć ten nadmuchiwany element. Kiedy inflater nadmuchuje XML-a, to zwykle w tym XML-u jest korzeń i jego potomkowie. Z potomkami nie ma żadnego problemu, bo dla każdego z nich w pliku XML widać, jakiej klasy jest jego rodzic, więc inflater może wybrać odpowiednią klasę LayoutParams. Ale z korzeniem tak się nie da - przy nadmuchiwaniu korzenia inflater musi wiedzę o klasie, której obiekt ma stworzyć, dostać z zewnątrz, albo nie stworzy obiektu layoutparams z parametrami lejałtowymi. I to dlatego przy nadmuchiwaniu lejałtu z XML-a możemy metodzie inflate przekazać obiekt typu ViewGroup, w którym będzie później umieszczony korzeń. Jeśli nie przekażemy go (czyli jeśli zamiast niego przekażemy null), to inflater nie wczyta dla korzenia parametrów lejałtowych i nie stworzy obiektu layoutparams z nimi i nie umieści tego obiektu layoutparams w tworzonym widoku. A jeśli przekażemy metodzie inflate ten kontener, w którym będzie umieszczony korzeń, to mamy dwie możliwości. Możemy metodzie inflate przekazać jeszcze trzeci parametr mówiący, czy inflater ma od razu podczepić nadmuchany korzeń temu rodzicowi. Jeśli w tym trzecim parametrze przekażemy true albo nie przekażemy go wcale (czyli wywołamy dwuargumentową wersję metody inflate), inflater nadmucha korzeń, wyciągnie z jego XML-a parametry lejałtowe, na kontenerze, w którym ma być umieszczony korzeń, zawoła metodę generateLayoutParams i przekaże jej te parametry lejałtowe wyciągnięte z XML-a, a metoda generateLayoutParams zwróci obiekt odpowiedniej klasy dziedziczącej po LayoutParams z zaszytymi w nim parametrami lejałtowymi, i wtedy inflater doda korzeń do tego kontenera przekazując ten obiekt layoutparams, i zwróci wcale nie, jak można by myśleć, widok będący wynikiem nadmuchania XML-a (czyli korzeń), tylko kontener, w którym umieszczony został korzeń. A jeśli przekażemy ten parametr i przekażemy w nim false, to inflater nadmucha korzeń, wyciągnie z jego XML-a parametry lejałtowe, na kontenerze, w którym ma być umieszczony korzeń, zawoła metodę generateLayoutParams i przekaże jej te parametry lejałtowe wyciągnięte z XML-a, a metoda generateLayoutParams zwróci obiekt odpowiedniej klasy dziedziczącej po LayoutParams z zaszytymi w nim parametrami lejałtowymi, a wtedy inflater weźmie ten obiekt i umieści go w korzeniu wołając na nim setLayoutParams i taki nadmuchany korzeń z zaszytym w nim obiekcie layoutparams zwróci. Dzięki czemu my, dostawszy ten nadmuchany obiekt, jak zechcemy go umieścić w kontenerze, to będziemy mogli go w tym kontenerze umieścić metodą addView nie zajmując się obiektem layoutparams - bo ten obiekt jest już w nadmuchanym obiekcie umieszczony.
Skoro ten kontener jest używany tylko do tego, żeby powiedzieć, jakiej klasy ma być obiekt layout params, to nasuwało mi to dwa powiązane ze sobą pytania. Po co przekazuje się obiekt tej klasy, a nie obiekt klasy Class reprezentujący samą klasę? Znaczy, dlaczego się nie pisze getLayoutInflater().inflate(R.layout.mójlayout, LinearLayout.class)? I czy byłoby to samo, jakbym przekazał nie ten kontener, w którym naprawdę potem umieszczę ten korzeń, a inny obiekt tej samej klasy? Wydaje mi się - choć dokładnie tego nie sprawdzałem - że przecież nieraz chcemy, żeby nadmuchując widok inflater na atrybuty z XML-a nakładał atrybuty z motywu, a do tego jest potrzebny kontekst, a ten kontekst może być wzięty z tego kontenera. Ale to jest tylko domysł, dokładnie tego nie sprawdzałem.
komentarze:
powrót na stronę główną
RSS