sobota, 27 września 2014

Pojedynek z Androidem - odc. 10 layout once again

Witam. Dziś pozwolę sobie powrócić do rozważań na temat layoutu na podstawie kolejnych filmów. Obecność TableLayout przy braku GridView wskazuje, że nie najnowszych.

W pierwszej części mamy podstawowe, znane od dawna layouty. Czy mi się podobają?  Jakie mam nowe uwagi czy porównania?

  • LinearLayout - jak już może kiedyś wspominałem dla mnie coś takiego ala StackPanel z XAML, ale z możliwością rozciągania dzieci do wypełnienia dostępnej przestrzeni, czyli jakby i część możliwości Grid-a (może coś ala flexbox z CSS). Zejdźmy na szczegóły. Layout_gravity to dla mnie jakby Horizontal/VerticalAlignment, gravity - Horizontal/VerticalContentAlignment z ContentControl (przy czym możemy mieć w Android wiele dzieci), a weight – odpowiednik podziału proporcjonalnego wyrażanego * w Grid.  WeightSum to może dobre wyjście, by nie definiować dodatkowych pustych bytów (choć trzeba o nim wiedzieć).
  • RelativeLayout - po przyjrzeniu się kilka razy może ujść, chociaż wydaje się to przegadane (w XAML z reguły najczęściej wystarczy operować sprawnie na StackPanel i Grid, w Android mamy więcej jak w CSS wyborów i możemy tą samą rzecz zrobić na wiele sposobów). Na pewno warto stosować, bo jest bardziej wydajne od LinearLayout, zwłaszcza zagnieżdżonych. Interesujące jest też wyrównanie do dolnej linii tekstu (nie ma czegoś takiego gotowego w XAML). Słabizną jest IMHO przeskakiwanie elementów przy ukrywaniu ich sąsiadów. Trzeba stosować dodatkowy parametr layout_alignWithParentIfMissing, aby temu zapobiec.
  • FrameLayout - jak się tak przyjrzeć, to ma pewne cechy Grid-a (elementy wstawione jako dalsze przysłaniają te wstawione wcześniej, mamy rozciąganie do wypełnienia przestrzeni, obsługuje wyrównania deklarowane przez dzieci, choć nie mamy wierszy i kolumn). Spójność i uniwersalność kontenera burzą moim zdaniem jego właściwości takie jak foreground czy foregroundGravity (może, gdy mamy mniej elementów w środku, to jest lepiej, ale kontener bardzo wyraźnie wiąże się z dzieckiem na najwyższej warstwie, a co z innymi dziećmi? )
  • TableLayout - hm… właściwie po co? Jakby wzorowanie się na CSS, gdzie też table średnio mi się podoba i w zasadzie to tylko do data gridów pasuje najlepiej. Ale rozumiem, przez ileś wersji nie było w Android odpowiednika Grid-a (obecnie mamy GridView), więc trzeba było sobie radzić przy użyciu TableLayout. Jak coś wrzucimy poza wierszami, to się rozciągnie na całą szerokość. Może i super, ale burzy spójność komponentu (hm… może znowu chodzi o wydajność). Co do rozciągania i ściskania wskazanych kolumn lub wszystkich mam mieszane uczucia, z jednej strony niby mogę precyzyjnie tym sterować, z drugiej strony to mocniejszy autorytaryzm rodzica i dodatkowe parametry, a podobne rzeczy mogłyby określać same kolumny (np. w DataGrid można określić minimalną, maksymalną szerokość lub podział proporcjonalny *)

Druga część to pokaz narzędzi do podglądania tworzonych elementów w drzewie wizualnym i wskazówki co do wydajności. Podgląd zbudowanych elementów w runtime kojarzy mi się z HTML pisanym komponentowo, gdzie też czasami z jakiegoś tagu może powstać więcej tagów (w XAML dla uczciwości też każda kontrolka user czy template jest wizualizowana za pomocą fragmentu XAML widzianego wyżej jako jeden element, ale nie ma w standardzie narzędzi do podglądu zbudowanego w ostatecznym rezultacie drzewa). Jak się okazuje podobnie może czasami się dziać w Androidzie  i to bez pisania własnych kontrolek!  Być może na małą skalę, ale jednak. W ograniczeniu liczby rodziców pomocny może być tag merge (tworzy jednego rodzica automatycznie). Include to jakaś forma reużytkowania powtarzalnych fragmentów interfejsu użytkownika (odpowiednik jakby wstrzyknięcia szablonu w Angularze, czy bardziej ogólnie jakby szablonu lub kontrolki XAML/HTML5). Może prosty prekursor bardziej rozbudowanych fragmentów?  Podobać się może ViewStub przewidziany do lazy loadingu, czyli to my decydujemy kiedy chcemy doładować jakiś fragment drzewa.  W XAML nie potrzebujemy jednak do tego dodatkowych tworów, ot doładowujemy kawałek drzewa, ale imperatywnie.  W Android mimo deklaratywności ViewStub i tak ładujemy go imperatywnie w kodzie, więc nie widzę tu dużego pożytku poza tym, że trzymamy cały XML w jednym miejscu.  W XAML albo robimy takie rzeczy sami w całości ręcznie albo dostajemy gotowe w niektórych kontrolkach np. Hub (Windows/Windows Phone 8.1). Androida można pochwalić za dobre narzędzia analityczne, wskazujące jak można zoptymalizować ułożony przez siebie layout w XML oraz narzędzia pokazujące czas mierzenia, układania i rysowania poszczególnych elementów drzewa. W mobilnych aplikacjach XAML/HTML5 również mamy narzędzia związane z mierzeniem wydajności, chociaż wygląda to trochę inaczej. Przykładowo mierząc responsywność aplikacji XAML możemy się dowiedzieć, ile czasu było potrzebne na sparsowanie, ułożenie danego elementu, ile zajął kod aplikacji czy inne operacje XAML. Wracając do komponentów w Android ImageView ma pewne cechy Image z XAML, gdzie też można zadecydować o sposobie rozciągania obrazka wewnątrz przestrzeni czy ustawić choćby tło. Jeśli chodzi o TextView to już taki atrybut jak choćby drawableLeft to IMHO zaburzenie czystości formy pola tekstowego, ale może to być wygodne i bardziej wydajne niż budowanie samemu takiego szablonu.

Poniżej jak zawsze szczegóły.

 

Podstawowe layouty

  • LinearLayout
  • RelativeLayout
  • FrameLayout
  • TableLayout

Podstawowe atrybuty

  • size
    • match-parent - cała dostępna przestrzeń
    • wrap-content – tyle, ile zajmuje element (auto)
  • margin vs padding
  • gravity
    • layout_gravity:  na zewnątrz np. center_horizontal,  center_horizontal | bottom (centralnie na dole)
    • gravity:  wewnątrz elementu, może oddziaływać na więcej niż jeden element w środku

Przeliczanie z dp na px

px = dp * density

density = bucket(dpi / 160);

LinearLayout

  • layout_gravity - nie działa w kierunku orientacji danego LinearLayout
  • layout_weight + rozmiar = 0dp - podział proporcjonalny (*), jeśli jeden element będzie na wrap-content to reszta podzieli pozostałą przestrzeń proporcjonalnie

Wycentrowana 1/3

<LinearLayout  android:layout_width=”match_parent”

      android:layout_height=”match_parent”

      android: orientation=”horizontal” >

       <View android:layout_width=”0dp”

               android:layout_height=”1dp”

               android:layout_weight=”1” />

       <TextView android:layout_width=”0dp”

               android:layout_height=”wrap_content”

               android:layout_weight=”1”

               android:text=”@string/text1”

               android:background=”#cf9” />

       <View android:layout_width=”0dp”

               android:layout_height=”1dp”

               android:layout_weight=”1”  />

</LinearLayout>

Lepiej z sumą wag

<LinearLayout …

         android:gravity=”center_horizontal”

         android:weightSum=”3” >

         <TextView …

                 android:layout_weight=”1” />

</LinearLayout>

RelativeLayout

Względne

  • pozycja  np. layout_below,  layout_toLeftOf
  • wyrównanie  np. layout_alignParentRight

do

  • rodzica
    • layout_alignParentTop
    • layout_alignParentBottom
    • layout_alignParentLeft
    • layout_alignParentRight
    • layout_centerHorizontal
    • layout_centerVertical
    • layout_centerInParent
  • sąsiada
    • layout_above
    • layout_below
    • layout_toLeftOf
    • layout_toRightOf

Domyślnie lewy górny róg

Czasami trzeba więcej niż jeden np. layout_below + layout_toLeftOf

Czasami potrzebne dodatkowe wyrównanie np. w elemencie nad danym elementem, tak by zaczynały się oba równo np. layout_above + layout_alignLeft

Dodatkowe parametry do wyrównania:

  • layout_alignLeft
  • layout_alignTop
  • layout_alignRight
  • layout_alignBottom
  • layout_alignBaseline (wyrównanie do dolnej linii tekstu we wskazanym elemencie)

android:visibility

  • ”invisible”  - niewidoczny, ale zajmuje przestrzeń (jak hidden)
  • “gone”  - niewidoczny, nie zajmuje przestrzeni (jak collapsed)

Jak element, do którego się odnosimy w innym elemencie jest “gone” to layout tego drugiego elementu ignoruje położenie względem niego

Dodatkowy parametr, by zapobiec przeskakiwaniu elementów przy ukrywaniu innych:  layout_alignWithParentIfMissing = “true”

FrameLayout

<FrameLayout

        android:layout_width=”match_parent”

        android:layout_height=”match_parent”>

        <View

                android:layout_width=”240dp”

                android:layout_height=”180dp”

                android:layout_gravity=”center”

                android:background=”#9cf” />

         <View

                android:layout_width=”180dp”

                android:layout_height=”240dp”

                -- android:layout_gravity=”right|center_vertical”

                android:layout_margin=”16dp”

                android:background=”#c9f” />

  </FrameLayout>

Drugi element przysłania pierwszy

<FrameLayout

         android:layout_width=”wrap_content”

         android:layout_height=”wrap_content”  >

        <ImageView

                android:layout_width=”wrap_content”

                android:layout_height=”wrap_content”

                android:src=”@drawable/image1” />

         <ImageView

                android:layout_width=”wrap_content”

                android:layout_height=”wrap_content”

                android:src=”@drawable/image2” />

</FrameLayout>

<FrameLayout

         android:layout_width=”wrap_content”

         android:layout_height=”wrap_content”

         android:foreground=”@drawable/image2”

         android:foregroundGravity=”top|left” >

         <ImageView

                android:layout_width=”wrap_content”

                android:layout_height=”wrap_content”

                android:src=”@drawable/image1” />

</FrameLayout>

foreground:  obrazek jest rozciągany, by wypełnić całą otaczającą go przestrzeń

TableLayout

Domyślnie TableRow

  • layout_width=”match_parent”
  • layout_height=”wrap_content”

Domyślnie dzieci TableRow

  • layout_width=” wrap_content”
  • layout_height=”wrap_content”

<TableLayout

         android:layout_width=”wrap_content”

         android:layout_height=”wrap_content”>

         <TableRow>

                    <TextView …

                             android:layout_column=”1”

                             android:layout_span=”2” />

                   <TextView …/>

                   <TextView …/>

         </TableRow>

        <TableRow>

                    <TextView …/>

                   <TextView …/>

                   <TextView …/>

        </TableRow>

</TableLayout>

Pojedyncza komórka w wierszu – tam, gdzie powinna być, reszta wiersza pusta

Liczenie wierszy i kolumn od zera

Jak wrzucimy coś bezpośrednio do TableLayout, to się rozciągnie na całą szerokość

<TableLayout

         android:layout_width=”wrap_content”

         android:layout_height=”wrap_content”>

        <TextView … />

         <TableRow>

                    <TextView …

                             android:layout_column=”1”

                             android:layout_span=”2” />

                   <TextView …/>

                   <TextView …/>

         </TableRow>

        <TableRow>

                    <TextView …/>

                   <TextView …/>

                   <TextView …/>

        </TableRow>

</TableLayout>

 

<TableLayout

          android: shrinkColumns=”2”  …>

          …

</TableLayout>

Wyrównanie elementu do wysokości wiersza  - android:layout=”match_parent”

TableLayout z szerokością match_parent nie rozciąga się

Rozciąganie kolumn

<TableLayout

          android: stretchColumns=”0,1,2”  …>  //rozciągaj kolumnę 2,  * – wszystkie

          …

</TableLayout>

Ukrywanie kolumn

<TableLayout

          android: collapseColumns=”3”  …>  //ukryj kolumnę 3,  pozostaje zarezerwowana przestrzeń przy stretchColumns=”*”

          …

</TableLayout>

Wybór layoutu

1 wymiar

  • LinearLayout  (oś x lub y)
  • FrameLayout (oś z)

2 wymiary

  • RelativeLayout
  • TableLayout

Etapy:

  1. Measure - każdy element: width, height
  2. Layout - każdy element: left, top (right, bottom wynikają z rozmiarów elementu)

Ilość przebiegów przy pomiarze:

  • LinearLayout - 1  (2 z weight)
  • RelativeLayout  - 2
  • FrameLayout  - 1
  • TableLayout - 2 (3 przy ściskaniu i rozciąganiu kolumn)

Hierarchy Viewer

  • narzędzie do pokazywania struktury layoutu z właściwościami
  • możliwość sprawdzenia każdej aplikacji na emulatorze czy urządzeniu
  • pobieramy ViewServer.java z https://github.com/romainguy/ViewServer
  • dodajemy pozwolenie INTERNET
  • włączamy ViewServer w naszej aktywności (przykład: ViewServerActivity.java)

Android Studio

  • Android Device Monitor –> Hierarchy View Perspective
    • podgląd i nawigacja po hierarchii
    • pokazanie dla każdego elementu, jak kosztowne są:
      • pomiar
      • layout
      • rysowanie

Merge  - wstawienie bezpośrednie wszystkich elementów do parenta

      Jeden FrameLayout zamiast dwóch.

<merge …>

       <View …/>

       <View …/>

</merge>

       Znalezienie automatycznie tworzonego parenta

View parent = findViewById(android.R.id.content);

Include

Android Studio

  • res –> New Resource File:  typ - Layout, root – ImageView

       heart.xml:    <ImageView …/>     

<LinearLayout …>

        <include layout=”@layout/heart” />

        <include layout=”@layout/heart”

                android:layout_width=”wrap_content”

                android:layout_height=”wrap_content”

                android:layout_marginTop=”16dp”/>

       <include layout=”@layout/heart” />

</LinearLayout>

ViewStub

  • podobny do include
  • element nie jest ładowany do drzewa, dopóki nie jest potrzebny (domyślnie elementy są tworzone w drzewie nawet przy visibility=”gone”)

<RelativeLayout …>

         <TextView …/>

         <Button …/>

         <ViewStub

                  android:id=”@+id/desc”

                  android:layout=”@layout/checkerboard”

                  android:layout_width=”wrap_content”

                  android:layout_height=”wrap_content”

                  android:layout_below=”@id/title”

                  android:visibility=”gone”  />

</RelativeLayout>

       Załadowanie/pokazanie lub ukrycie:

if (mDescView == null)  {

         mDescView = ((ViewStub) findViewById(R.id.desc)).inflate();

}

boolean visible = (mDescView.getVisibility() == View.VISIBLE);

mDescView.setVisibility(visible ? View.GONE : View.VISIBLE);

Optymalizacja

Android Studio

  • Analyse –> Inspect Code…  (Android Lint)

Obrazek z tekstem - wersja odchudzona

<TextView

          android:layout_width=”wrap_content”

          android:layout_height=”wrap_content”

          android:drawableLeft=”@drawable/heart”

          android:textSize=”20sp”

          android:text=”@string/hello” />

Pole edytowalne wypełniające całą przestrzeń, a za nim przycisk - wydajniej w RelativeLayout niż LinearLayout

Flaga Wietnamu – czerwony prostokąt z żółtą gwiazdą w środku

<ImageView

           android:layout_width=”240dp”

           android:layout_height=”160dp”

           android:layout_gravity=”center”

           android:background=”@color/red”

           android:src=”@drawable/yellow_star”

           android:scaleType=”center” />   //obrazek nie jest rozciągany, tylko wycentrowany

Brak komentarzy: